
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart' as iOS;
import 'package:flutter/services.dart';
import 'package:xsdk_flutter/xsdk/framework/base/xsdk_widget_stateful.dart';
import 'package:xsdk_flutter/xsdk/framework/base/xsdk_widget_stateless.dart';
import 'package:xsdk_flutter/xsdk/framework/bean/x_bean_data_table.dart';
import 'package:xsdk_flutter/xsdk/framework/bean/x_bean_group_list_item.dart';
import 'package:xsdk_flutter/xsdk/framework/base/xsdk_function.dart';
import 'package:xsdk_flutter/xsdk/framework/component/searchbar/x_component_searchbar.dart';
import 'package:xsdk_flutter/xsdk/framework/widget/xsdk_base_wave.dart';
import 'package:xsdk_flutter/xsdk/framework/widget/xsdk_button_timer.dart';
import 'package:xsdk_flutter/xsdk/framework/widget/xsdk_divider_dashedLine.dart';
import 'package:xsdk_flutter/xsdk/framework/widget/xsdk_listView_reorderable.dart';
import 'package:xsdk_flutter/xsdk/operation/x_constant_temp.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_color.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_flutter.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_log.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_number.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_string.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_style.dart';

/*


SafeArea


远程动态UI:
https://pub-web.flutter-io.cn/packages/rfw

延时显示widget:  https://pub.flutter-io.cn/packages/delayed_display
省市区widget: https://pub.flutter-io.cn/packages/csc_picker

标签:
https://pub.flutter-io.cn/packages/material_tag_editor
https://pub.flutter-io.cn/packages/textfield_tags
https://pub.flutter-io.cn/packages/flutter_chip_tags
https://pub.flutter-io.cn/packages/simple_tags
https://pub.flutter-io.cn/packages/flutter_tags_x
https://pub.flutter-io.cn/packages/flutter_tags

布局动画(列表,表格): https://pub.flutter-io.cn/packages/flutter_staggered_animations
流式布局: https://pub.flutter-io.cn/packages/flutter_staggered_grid_view

日历时间计划: https://pub.flutter-io.cn/packages/flutter_week_view


动态闪亮widiget: https://pub-web.flutter-io.cn/packages/shimmer

 */


/*
阴影效果:
shadows: [
  Shadow(
    color: Colors.cyanAccent,
    offset:Offset(1,1),
    blurRadius: 10,
  ),
  Shadow(
    color: Colors.blue,
    offset:Offset(1,1),
    blurRadius: 10,
  ),
],
 */


/*
  Overlay //浮窗
  IgnorePointer //忽略点击事件，不影响下层的点击事件
  AbsorbPointer //消费掉点击事件，下层也收不到，不会做出相应
   */

//工具类使用abstract定义时,写代码时不会提示创建实例选项
abstract class XWidgetUtils{

  //底部菜单高度
  static double getBottomBarHeight(){
    return XTempData.defaultBottomBarHeight??(XFlutterUtils.isiOS()?83:75);
  }

  //集成封装///////////////////////////////////////////////////////////////////

  //搜索条
  static Widget getXSearchBarWidget({
    required XFunctionResultCallback<String>? searchResultCallback,
    required XFunctionWidgetOnClick<String?>? onTapSearch,
    String? initSearchValue, //初始值
    String? hintText = '搜索内容',
    List<Widget>? rightCustomWidgets,
    bool isShowLeftBackIcon = false,
    GestureTapCallback? onTapRightCustomIcon,
    Color outSideBackgroundColor = Colors.white,
    Color? searchBarBackgroundColor,
    Color iconColor = XColorUtils.colorCCCCCC,
    Color textColor = XColorUtils.color333333,
    Color hintTextColor = XColorUtils.colorCCCCCC,
    EdgeInsets? margin,
  }) {
    //自定义右侧图标
    List<Widget> outsideRightCustomWidgets = [];
    if (rightCustomWidgets != null && rightCustomWidgets.isNotEmpty) {
      // 自定义图标
      /*
      outsideRightCustomWidgets.add(XWidgetUtils.getWidgetGestureDetector(
        child: XWidgetUtils.getLayoutPadding(
            padding: XWidgetUtils.getEdgeInsets(0, 0, 10, 0),
            child: XImageUtils.getImageWidgetWithAsset(
              'shaixuan_5.png',
              width: 24,
              height: 24,
              color: iconColor,
            )),
        onTap: onTapRightCustomIcon,
      ));
       */
      outsideRightCustomWidgets.addAll(rightCustomWidgets);
    }

    return XStatefulWidget(() => XComponentSearchBarState(
      initSearchValue: initSearchValue,
      searchResultCallback: searchResultCallback,
      isShowLeftBackIcon: isShowLeftBackIcon,
      hintText: hintText,
      outsideRightCustomWidgets: outsideRightCustomWidgets,
      outSideBackgroundColor: outSideBackgroundColor,
      searchBarBackgroundColor: searchBarBackgroundColor,
      iconColor: iconColor,
      textColor: textColor,
      hintTextColor: hintTextColor,
      onTapSearch: onTapSearch,
      margin: margin,
    ));
  }

  //圆形布局
  static Widget getXLayoutCircle(
      {
        Widget? child,
        required double width,
        required double height,
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? padding,//getEdgeInsets(left,top,right,bottom)
        Color? backgroundColor=const Color(0xFFFFFFFF),
        String? backgroundAssetImageName,
        Color? borderColor,//边框颜色
        double borderWidth=0.5,
        Alignment? alignment,//对齐
        bool isUseDefaultBoxShadow=false,//是否使用默认阴影
      }
      ){

    //实测:加载网络图片使用CircleAvatar时失效,
    // return XWidgetUtils.getLayoutSizedBox(
    //   CircleAvatar(
    //     backgroundColor: backgroundColor,
    //     backgroundImage: XStringUtils.isEmpty(backgroundAssetImageName)?null:AssetImage('assets/images/$backgroundAssetImageName'),
    //     child: child,
    //   ),
    //   width: width,
    //   height: height,
    // );

    //用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图等
    BoxDecoration decoration=getBoxDecoration(
      backgroundColor: backgroundColor,
      border: (borderColor==null||borderWidth<=0)?null:Border.all(width: borderWidth, color: borderColor),
      backgroundImageNameInAssets: backgroundAssetImageName,
      isCircle: true,
      isUseDefaultBoxShadow: isUseDefaultBoxShadow,
    );

    return getLayoutContainer(
      child: child,
      margin: margin,
      padding: padding,
      width: width,
      height: height,
      decoration: decoration,
      alignment:alignment,
    );

  }


  //带圆角布局(没有边框, 圆角会截取超出部分)
  static Widget getXLayoutRoundedRect(
      {
        required Widget child,
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? padding,//getEdgeInsets(left,top,right,bottom)
        Color? backgroundColor=const Color(0xFFFFFFFF),
        double? radius, //
        double radius_topLeft=10,
        double radius_topRight=10,
        double radius_bottomLeft=10,
        double radius_bottomRight=10,
        double? width,//注意:不能设置占满double.infinity,否则报错
        double? height,//注意:不能设置占满double.infinity,否则报错
        Alignment? alignment,//对齐 注意:为null值则按view的实际宽度,如果设置值则占满剩余空间
        BoxConstraints? boxConstraints, //用于限制最小尺寸, const BoxConstraints.expand()扩展到最大,优先级会高于width和height属性  BoxConstraints(minWidth:minWidth,maxWidth:maxWidth,minHeight:minHeight,maxHeight:maxHeight,),
      }
      ){

    return getLayoutContainer(
        margin: margin,
        width:width,
        height:height,
        boxConstraints: boxConstraints,
        child: getLayoutClipRRect(
          getLayoutContainer(
            child: child,
            padding: padding,
            alignment:alignment,
            backgroundColor: backgroundColor,
          ),
          radius: radius,
          radius_topLeft: radius_topLeft,
          radius_topRight: radius_topRight,
          radius_bottomLeft: radius_bottomLeft,
          radius_bottomRight: radius_bottomRight,
        )
    );
  }

  //带圆角布局(有边框)
  static Widget getXLayoutRoundedRectWithBorder(
      {
        required Widget child,
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? padding,
        double? width,//注意:不能设置占满double.infinity,否则报错
        double? height,//注意:不能设置占满double.infinity,否则报错
        Color? backgroundColor=const Color(0xFFFFFFFF),
        Color? borderColor,//边框颜色
        double? borderWidth,
        Alignment? alignment,//对齐,注意:为null值则按view的实际宽度,如果设置值则占满剩余空间
        double radius_topLeft=10,
        double radius_topRight=10,
        double radius_bottomLeft=10,
        double radius_bottomRight=10,
        bool isUseDefaultBoxShadow=false,//是否使用默认阴影
      }
      ){

    borderColor??=XColorUtils.dividerColor;
    borderWidth??=XTempData.defaultDividerHeight;

    //用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图等
    BoxDecoration decoration=getBoxDecoration(
      backgroundColor: backgroundColor,
      border: borderWidth<=0?null:Border.all(width: borderWidth, color: borderColor),
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(radius_topLeft),
        topRight: Radius.circular(radius_topRight),
        bottomLeft: Radius.circular(radius_bottomLeft),
        bottomRight: Radius.circular(radius_bottomRight),
      ),
      isUseDefaultBoxShadow: isUseDefaultBoxShadow,
    );

    return getLayoutContainer(
      child: child,
      margin: margin,
      padding: padding,
      width: width,
      height: height,
      decoration: decoration,
      clipBehavior: Clip.antiAlias, // 核心属性：开启裁剪
      alignment:alignment,
    );
  }

  //左右对齐内容bar,支持点击
  /*
  rightWidget: XImageUtils.imageLocal(
                'assets/images/x_arrow_right.png',
                width:7,
                height: 13
            )
   */
  static Widget getXBarForLeftRightDefault(
      {
        required String leftText,
        EdgeInsets? leftTextPadding,
        required String rightText,
        EdgeInsets? rightTextPadding,
        TextStyle? leftTextStyle,
        TextStyle? rightTextStyle,
        Widget? leftWidget,//自定义左widget,如:箭头,已选等图标
        Widget? rightWidget,//自定义右widget,如:箭头,已选等图标
        double? height,//包括分隔线的总高度(即可设置为listItem的高度相同)
        Color? backgroundColor=Colors.white,//设置背景色,空白处也可点击
        Color? borderColor,//边框颜色
        double? borderWidth,
        double radius_topLeft=10,
        double radius_topRight=10,
        double radius_bottomLeft=10,
        double radius_bottomRight=10,
        String? backgroundImageNameInAssets,//背景图名称(在Assets文件夹中) 'guide_bg.png'
        EdgeInsets? margin,
        EdgeInsets? padding,
        VoidCallback? onPressed,
        bool isShowTopDivider=false,
        bool isShowBottomDivider=true,
        Color? dividerColor,
        bool isLeftExpanded=false,
        double? leftWidth,
      }
      )
  {

    dividerColor??=XColorUtils.dividerColor;

    List<Widget> rightWidgetList=[];
    rightWidgetList.add(getWidgetText(
      rightText,
      style: rightTextStyle??XStyleUtils.getTextStyle(
          fontSize: XTempData.defaultTextFontSize,
          color: XColorUtils.color333333
      ),
      padding: rightTextPadding,
    ));

    if(rightWidget!=null){
      //右边自定义widget
      rightWidgetList.add(getLayoutPadding(
          padding: getEdgeInsets(5, 0, 0, 0),
          child: rightWidget
      ));
    }

    List<Widget> leftWidgetList=[];
    if(leftWidget!=null){
      //左边自定义widget
      leftWidgetList.add(getLayoutPadding(
          padding: getEdgeInsets(0, 0, 5, 0),
          child: leftWidget
      ));
    }
    leftWidgetList.add(
        getWidgetText(
          leftText,
          style: leftTextStyle??XStyleUtils.getTextStyle(
            fontSize: XTempData.defaultTextFontSize,
            color: XColorUtils.color333333,
          ),
          padding: leftTextPadding,
          maxLines: 1,
        )
    );


    Widget leftRow=getLayoutContainer(
        margin: getEdgeInsets(0, 0, 10, 0),
        width: leftWidth,
        alignment: Alignment.centerLeft,
        child: getLayoutRow(
            children: leftWidgetList
        )
    );

    Widget rightRow=getLayoutRow(
        mainAxisAlignment:MainAxisAlignment.end,
        children: rightWidgetList
    );


    return getXBarForLeftRightWidget(
      leftWidget: isLeftExpanded?getLayoutExpanded(leftRow):leftRow,
      rightWidget: isLeftExpanded?rightRow:getLayoutExpanded(rightRow),
      height:height,
      backgroundColor:backgroundColor,
      backgroundImageNameInAssets:backgroundImageNameInAssets,
      margin:margin,
      padding:padding,
      onPressed:onPressed,
      isShowTopDivider:isShowTopDivider,
      isShowBottomDivider:isShowBottomDivider,
      dividerColor:dividerColor,
      borderColor:borderColor,
      borderWidth:borderWidth,
      radius_topLeft:radius_topLeft,
      radius_topRight:radius_topRight,
      radius_bottomLeft:radius_bottomLeft,
      radius_bottomRight:radius_bottomRight,
    );


  }

  //左右Widget,注意: 左右widget其中一个要使用XWidgetUtils.getLayoutExpanded(child)包裹,否则界面有时不显示右边widget
  static Widget getXBarForLeftRightWidget(
      {
        required Widget leftWidget,
        required Widget rightWidget,
        double? height,//包括分隔线的总高度(即可设置为listItem的高度相同)
        Color? backgroundColor=Colors.white,//设置背景色,空白处也可点击
        Color? borderColor,//边框颜色
        double? borderWidth,
        double radius_topLeft=10,
        double radius_topRight=10,
        double radius_bottomLeft=10,
        double radius_bottomRight=10,
        String? backgroundImageNameInAssets,//背景图名称(在Assets文件夹中) 'guide_bg.png'
        EdgeInsets? margin,
        EdgeInsets? padding,
        VoidCallback? onPressed,
        bool isShowTopDivider=false,
        bool isShowBottomDivider=true,
        Color? dividerColor,
      }
      )
  {

    dividerColor??=XColorUtils.dividerColor;
    height??=XTempData.defaultBarHeight;

    //分割线
    List<Widget> list0=[];
    double centerHeight=height;

    //是否显示分割线
    if(isShowTopDivider){
      list0.add(getWidgetDivider(color: dividerColor));
    }

    //计算中间高度
    if(isShowTopDivider){
      centerHeight=centerHeight-XTempData.defaultDividerHeight-0.5;
    }
    if(isShowBottomDivider){
      centerHeight=centerHeight-XTempData.defaultDividerHeight-0.5;
    }

    //左右两边内容
    List<Widget> listLeftright=[];
    listLeftright.add(leftWidget);
    listLeftright.add(rightWidget);

    list0.add(
        getLayoutContainer(
          padding: padding??EdgeInsets.fromLTRB(XTempData.defaultLeftRightWidgetMarginLeft, 0, XTempData.defaultLeftRightWidgetMarginRight, 0),
          height: centerHeight,
          alignment: Alignment.centerLeft,
          child: getLayoutRow(
            crossAxisAlignment:CrossAxisAlignment.center,
            children: listLeftright,
          ),
        )
    );

    //是否显示分割线
    if(isShowBottomDivider){
      list0.add(getWidgetDivider(color: dividerColor));
    }

    //用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图等
    BoxDecoration? decoration=getBoxDecoration(
      backgroundColor: backgroundImageNameInAssets!=null?null:backgroundColor,
      border: (borderColor==null)?null:Border.all(width: borderWidth??XTempData.defaultDividerHeight, color: borderColor),
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(radius_topLeft),
        topRight: Radius.circular(radius_topRight),
        bottomLeft: Radius.circular(radius_bottomLeft),
        bottomRight: Radius.circular(radius_bottomRight),
      ),
      backgroundImageNameInAssets: backgroundImageNameInAssets,
    );

    return getWidgetGestureDetector(
      child: getLayoutContainer(
        decoration: decoration,
        height: height,
        margin: margin,
        child:getLayoutColumn(
            mainAxisAlignment:MainAxisAlignment.center,
            children: list0
        ),
        alignment:Alignment.center,
      ),
      onTap: onPressed,
    );

  }


  //圆角按钮
  static Widget getXButtonRoundedRectAngle(
      {
        required String text,
        double? fontSize,
        Color fontColor=Colors.black,
        FontWeight fontWeight=FontWeight.normal,
        required VoidCallback onPressed,
        VoidCallback? onLongPress,
        double? width,//为圆角按钮的宽度,注意:如果在row中有多个,则需要设置固定宽度,否则报错
        double? height,//为圆角按钮的高度
        //按钮默认style
        double? radius,//按钮圆角半径
        Color backgroundColor=Colors.white,//按钮背景颜色
        Color? borderColor,//边框颜色
        double? borderWidth,
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? padding,//getEdgeInsets(left,top,right,bottom) //注意:这里处理不包括在总高度中,使height为圆角按钮的高度
      }
      ){

    borderColor??=XColorUtils.dividerColor;
    //borderWidth??=XTempData.defaultDividerHeight; //去除,能为0或null

    return getWidgetGestureDetector(
        child:getLayoutContainer(
          width:width,
          height:height??XTempData.defaultBottomHeight,
          margin:margin,
          padding:padding,
          alignment:Alignment.center,
          decoration: getBoxDecoration(
            backgroundColor: backgroundColor,
            border: (borderWidth==null||borderWidth<=0)?null:Border.all(width: borderWidth, color: borderColor),
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular((radius??XTempData.defaultBottomRadius)*XTempData.deviceScreenScale),
              topRight: Radius.circular((radius??XTempData.defaultBottomRadius)*XTempData.deviceScreenScale),
              bottomLeft: Radius.circular((radius??XTempData.defaultBottomRadius)*XTempData.deviceScreenScale),
              bottomRight: Radius.circular((radius??XTempData.defaultBottomRadius)*XTempData.deviceScreenScale),
            ),
          ),
          child:getWidgetText(
              text,
              style: XStyleUtils.getTextStyle(
                fontSize: fontSize??XTempData.defaultButtonFontSize,
                color:fontColor,
                lineHeight: 1,
                fontWeight: fontWeight,
              )
          ),
        ),
        onTap:onPressed,
        onLongPress:onLongPress

    );
  }

  /*
  将子控件反转(水平,垂直)
  Transform(
  alignment: Alignment.center, // 设置变换中心点
  transform: Matrix4.identity()..scale(-1.0, 1.0), // 水平翻转
  child: YourWidget(), // 需要翻转的控件
)
   */
  static Widget getLayoutTransform(
      Widget child,
      {
        bool isFlipX=true,//水平翻转
        bool isFlipY=false,//垂直翻转
      }
      ){
    return Transform.flip(
      flipX: isFlipX, // 水平翻转
      flipY: isFlipY, // 垂直翻转
      child: child,
    );
  }


  //基础base///////////////////////////////////////////////////////////////////

  /*
  获得文字占空间大小 计算 测量文字大小
   */
  static Map<String,double> getTextWidgetSpace(
      String? text,
      {
        TextStyle? style,
        List<InlineSpan>? children,
        double maxWidth = double.infinity,
      }
      ){

    Map<String,double> map={};
    map['width']=0;
    map['height']=0;

    if(!XStringUtils.isEmpty(text)||children!=null){
      TextSpan textSpan= TextSpan(
        text:text,
        children:children,
        style:style,
      );
      TextPainter textPainter=TextPainter(
          text: textSpan,
          textDirection:TextDirection.ltr
      );
      textPainter.layout(maxWidth:maxWidth);

      map['width']=textPainter.width;
      map['height']=textPainter.height;
    }

    return map;
  }

/*
EdgeInsets是属性值,
与Padding区别: Padding是widget
 */
  static EdgeInsets getEdgeInsets(
      double left,
      double top,
      double right,
      double bottom
      )
  {
    return EdgeInsets.fromLTRB(
        left*XTempData.deviceScreenScale,
        top*XTempData.deviceScreenScale,
        right*XTempData.deviceScreenScale,
        bottom*XTempData.deviceScreenScale
    );
  }

  /*
  BoxDecoration  Widget(layout)的装饰，使其改变其显示形式
  用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图等
  如: Container布局结合使用
  https://www.jianshu.com/p/ba7eb561ba17

  背景色设置为渐变色
  BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [Color(0xFFffffff), Color(0xFFEC592F)]),
          borderRadius: BorderRadius.all(Radius.circular(borderRadius)))

  BoxDecoration(
  border: new Border.all(color: Color(0xFFFFFF00), width: 0.5), // 边色与边宽度
// 环形渲染
  gradient: RadialGradient(colors: [Color(0xFFFFFF00), Color(0xFF00FF00), Color(0xFF00FFFF)],radius: 1, tileMode: TileMode.mirror)
//扫描式渐变
//        gradient: SweepGradient(colors: [Color(0xFFFFFF00), Color(0xFF00FF00), Color(0xFF00FFFF)], startAngle: 0.0, endAngle: 1*3.14)
// 线性渐变
//        gradient: LinearGradient(colors: [Color(0xFFFFFF00), Color(0xFF00FF00), Color(0xFF00FFFF)], begin: FractionalOffset(1, 0), end: FractionalOffset(0, 1))
),

ShapeDecoration(
  color: Color(0xFFFF00FF), // 底色
  // 统一四边颜色和宽度
  shape: Border.all(color: Color(0xFF00FFFF),style: BorderStyle.solid,width: 2)
// 四个边分别指定颜色和宽度， 当只给bottom时与UnderlineInputBorder一致效果
//          shape: Border(top: b, bottom: b, right: b, left: b)
// 底部线
//          shape: UnderlineInputBorder( borderSide:BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2))
// 矩形边色
//        shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2))
// 圆形边色
//        shape: CircleBorder(side: BorderSide(color: Color(0xFFFFFF00), style: BorderStyle.solid, width: 2))
// 体育场（竖向椭圆）
//        shape: StadiumBorder(side: BorderSide(width: 2, style: BorderStyle.solid, color: Color(0xFF00FFFF))
// 角形（八边角）边色
//          shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2))

BoxDecoration decoration=getBoxDecoration(
      backgroundColor: backgroundColor,
      borderRadius: BorderRadius.all(Radius.circular(radius)),
      shape: BoxShape.rectangle,
    );

   */
  static BoxDecoration getBoxDecoration(
      {
        Border? border,//边框 ,如:Border.all(color:, style: BorderStyle.solid, width:1),底部间隔线: Border(bottom: BorderSide(color: XXX))
        BorderRadius? borderRadius,//设置全圆角: BorderRadius.all(Radius.circular(10)), 单侧圆角BorderRadius.only(topLeft: Radius.circular(64)),
        Color? backgroundColor,//背景色,注意:在这里设置背景色,就不能在Container中设置背景色,否则会报错
        String? backgroundImageNameInAssets,//背景图名称(在Assets文件夹中) 'guide_bg.png'
        int? backgroundImageWidth,
        int? backgroundImageHeight,
        Rect? centerSlice,//Rect.fromLTRB(12, 12, 12, 12), // 设置拉伸区域（左上右下,使用.9图
        BoxFit fit=BoxFit.fill,
        bool isCircle=false,//是否圆形,形状:默认矩形
        Gradient? gradient,//渐变
        /*
      阴影
  boxShadow: [
  BoxShadow(
    offset: Offset(6, 7), // 阴影的偏移量
    color: Color.fromRGBO(16, 20, 188, 1), // 阴影的颜色
    blurRadius: 5, // 阴影的模糊程度
    spreadRadius: 0, // 扩散的程度，如果设置成正数，则会扩大阴影面积；负数的话，则会缩小阴影面积
  )
]
       */
        List<BoxShadow>? boxShadows,
        bool isUseDefaultBoxShadow=false,
      }
      ){


    if(boxShadows==null && isUseDefaultBoxShadow){
      boxShadows=[
        const BoxShadow(
          offset: Offset(1, 1), // 阴影的偏移量
          color: Color(0x1a000000), // 阴影的颜色
          blurRadius: 5, // 阴影的模糊程度
          spreadRadius: 0, // 扩散的程度，如果设置成正数，则会扩大阴影面积；负数的话，则会缩小阴影面积
        )
      ];
    }

    return BoxDecoration(
      border: border,
      borderRadius: isCircle?null:borderRadius, //注意:圆形时不能设置圆角,否则报错
      shape: isCircle?BoxShape.circle:BoxShape.rectangle,
      color:backgroundColor,
      image: XStringUtils.isEmpty(backgroundImageNameInAssets)?null:DecorationImage(
        image: (backgroundImageWidth!=null||backgroundImageHeight!=null)?ResizeImage.resizeIfNeeded(backgroundImageWidth,backgroundImageHeight,AssetImage('assets/images/$backgroundImageNameInAssets')):AssetImage('assets/images/$backgroundImageNameInAssets'),
        centerSlice:centerSlice,
        fit: fit,
      ),
      boxShadow: boxShadows,
      /*
      渐变
      gradient: RadialGradient( // 圆形渐变，从中心开始渐变
  colors: [
    Color.fromRGBO(7, 102, 255, 1),
    Color.fromRGBO(3, 28, 128, 1)
  ]
),
线性渐变:
gradient: LinearGradient( // 线性渐变，如果没有指定开始和结束的地方，则默认从左边到右边
  colors: [
    Color.fromRGBO(7, 102, 255, 1),
    Color.fromRGBO(3, 28, 128, 1)
  ],
  begin: Alignment.topCenter, // 开始
  end: Alignment.bottomCenter, // 结束
),
       */
      gradient:gradient,
    );
  }


  //布局Layout////////////////////////////////////////////////////////////////

  /*
  带异步的布局 FutureBuilder

  //避免FutureBuilder频繁执行future方法
  Future<dynamic>? futureBuilderAsyncFunction;
  @override
  void initState() {
    super.initState();
    //避免FutureBuilder频繁执行future方法
    futureBuilderAsyncFunction=updateData();
  }

  return XWidgetUtils.getLayoutFutureBuilder(
        widget_state_done: onBuild(context),
        //asyncFunction: updateData() 注意:不能直接赋值future方法,会导致FutureBuilder频繁执行future方法,使用实例变量
        asyncFunction: futureBuilderAsyncFunction
    );

   */
  static FutureBuilder getLayoutFutureBuilder(
      {
        required XFunctionWidget widgetFunction,//完成,加载ListView等
        required Future<dynamic> asyncFunction,//内部await方法,返回data结果
        Widget? widget_state_none,
        Widget? widget_state_waiting,
        Widget? widget_state_active,
        Widget? widget_state_error,
      }
      ){

    return FutureBuilder(
        future: asyncFunction,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
          switch(snapshot.connectionState){
            case ConnectionState.none:
              XLogUtils.printLog('---ConnectionState.none');
              return widget_state_none??XDefaultStatelessWidget();
            case ConnectionState.waiting:
              XLogUtils.printLog('---ConnectionState.waiting');
              return widget_state_waiting??XDefaultStatelessWidget();
            case ConnectionState.active:
              XLogUtils.printLog('---ConnectionState.active');
              return widget_state_active??XDefaultStatelessWidget();
            case ConnectionState.done:
              XLogUtils.printLog('---ConnectionState.done,data=${snapshot.data}');
              if(snapshot.hasError){
                if(widget_state_error!=null){
                  return widget_state_error;
                }
              }
              return widgetFunction(snapshot.data);
          }

        }
    );
  }

  /*
  带异步的布局 StreamBuilder
   */
  static StreamBuilder getLayoutStreamBuilder(
      {
        required Stream stream, //streamController.stream
        required XFunctionWidget functionWidget,//完成,加载ListView等
      }
      ){
    return StreamBuilder(
        stream: stream,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
          return functionWidget(snapshot.data);
        }
    );
  }



/*
Container布局(最基本的布局),可让您创建矩形视觉元素。
可以使用如 background、一个边框、或者一个阴影,边距（margins）、填充(padding)和应用于其大小的约束(constraints)。
参数:
    width:null,则不限宽度,占满父控件,单位:dp
    height:null,同上,单位:dp
    背景:decoration(支持颜色,图片)和color(背景色)只能设置一个,注意:如果2个同时设置会报错;
  Container(
      height: 56.0, // 单位是逻辑上的像素（并非真实的像素，类似于浏览器中的像素）
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: new BoxDecoration(color: Colors.blue[500]),
      // Row 是水平方向的线性布局（linear layout）
      child: new Row(
        //列表项的类型是 <Widget>
        children: <Widget>[
          new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null 会禁用 button
          ),
          // Expanded expands its child to fill the available space.
          new Expanded(
            child: new Text('Expanded'),
          ),
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: (){
               ...
            },
          ),
        ],
      ),
    );


注意:Container+Column+Expanded会提示超过屏幕可视高度区域;因此需要改为Column+Expanded+Container
注意:如果外层是Expanded,如果内部itemlayout不重新使用row或Column或UnconstrainedBox去掉父类约束,则默认会占满可用空间(设置width固定大小无效)double.infinity

 transform: Matrix4.translationValues(2, 0, 0),//隐藏右边的边线
 或者使用Stack隐藏两边的边线
 XWidgetUtils.getLayoutStack(children: [
          XWidgetUtils.getLayoutPositioned(
            left: -1,
              right: -1,
              child: itemWidget)
              ])
 */

  static Container getLayoutContainer(
      {
        Key? key,
        Widget? child,
        double? width,
        double? height,//注意:不能设置占满double.infinity,否则报错
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom) //注意:不能有负数,即不能使用margin左移
        EdgeInsets? padding,//getEdgeInsets(left,top,right,bottom) //注意:不能有负数,即不能使用margin左移
        BoxDecoration? decoration, //getBoxDecoration(), 用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图,边线等
        Color? backgroundColor,//简单实现背景色
        Alignment? alignment,//对齐,注意:当为null时,子控件的Container大小设置无效,自动按父Container的大小铺满, 如果不为null,则按子Container实际大小
        Matrix4? transform,//旋转角度:  Matrix4.rotationZ(0.3), 左移: Matrix4.translationValues(-10, 0, 0),
        BoxConstraints? boxConstraints, //用于限制最小尺寸, const BoxConstraints.expand()扩展到最大,优先级会高于width和height属性  BoxConstraints(minWidth:minWidth,maxWidth:maxWidth,minHeight:minHeight,maxHeight:maxHeight,),
        Clip clipBehavior=Clip.none,//none超出不裁剪,实测: Clip.hardEdge裁剪报错?  ,Clip.antiAlias裁剪
      }
      )
  {
//注意:背景色:不能同时在decoration中设置和在Container设置,否则会报错
    if(backgroundColor!=null && decoration==null){
      decoration = getBoxDecoration(
        backgroundColor: backgroundColor,
      );
    }

    return Container(
      key: key,
      width: width==null?width:width*XTempData.deviceScreenScale,
      height: height==null?height:height*XTempData.deviceScreenScale, // 单位是逻辑上的像素（并非真实的像素，类似于浏览器中的像素）
      margin: margin,
      padding: padding,
      alignment: alignment,
      constraints: boxConstraints,//const BoxConstraints.expand(), //扩展到最大,优先级会高于width和height属性
      decoration: decoration,//用于设置布局/控件的圆角,边框,阴影,形状,背景色,背景图等
      //color: backgroundColor,//注意:在这里设置背景色,就不能在decoration中设置背景色,否则会报错,因此同其他布局一样吗,不在布局中设置背景色
      transform: transform,//旋转角度
      clipBehavior:clipBehavior,
      child: child,
    );
  }


  /*
  带圆角布局(简便),替代Container+BoxDecoration方式
   */
  static ClipRRect getLayoutClipRRect(
      Widget child,
      {
        double? radius,
        double radius_topLeft=10,
        double radius_topRight=10,
        double radius_bottomLeft=10,
        double radius_bottomRight=10,
      }
      ){
    if(radius!=null){
      radius=radius*XTempData.deviceScreenScale;
    }
    return ClipRRect(
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(radius??radius_topLeft*XTempData.deviceScreenScale),
        topRight: Radius.circular(radius??radius_topRight*XTempData.deviceScreenScale),
        bottomLeft: Radius.circular(radius??radius_bottomLeft*XTempData.deviceScreenScale),
        bottomRight: Radius.circular(radius??radius_bottomRight*XTempData.deviceScreenScale),
      ),//BorderRadius.zero:
      child: child,
    );
  }



/*
  Row 是水平方向的线性布局（linearlayout横向排列）
  注意:外套Container避免拨轮控件直接使用row提示宽度无限报错, 但有些时候需要定义宽度(最大宽度)

  IntrinsicHeight, 套到row的外层  widget 在Flutter中用于强制子 widget 的高度与其内容的最大高度相匹配。这在需要将具有不同高度的子 widget 放置在同一行或列中时非常有用。

   */
  static Widget getLayoutRow(
      {
        required List<Widget> children,
//max:各个items尽可能多占用水平方向的位置
        MainAxisSize mainAxisSize=MainAxisSize.max,
//左右对齐方式
        MainAxisAlignment mainAxisAlignment=MainAxisAlignment.start,
//上下对齐方式: 居中center
        CrossAxisAlignment crossAxisAlignment=CrossAxisAlignment.center,
        double? width,
      }
      ) {
    return getLayoutContainer(
        width: width,
        child: Row(
          mainAxisSize:mainAxisSize,
          mainAxisAlignment:mainAxisAlignment,
          crossAxisAlignment:crossAxisAlignment,
          children: children,
        )
    );
  }

/*
  Baseline 基线对齐
  Row+ Baseline
   */
  static Baseline getLayoutBaseline(
      {
        required double baseline,
/*
        TextBaseline.alphabetic 用于文字的底部对齐水平线
        TextBaseline.ideographic 用于排列表意文字的水平线。
         */
        TextBaseline baselineType=TextBaseline.alphabetic,
        Widget? child
      }
      ){
    return Baseline(
        baseline:baseline,
        baselineType:baselineType,
        child:child

    );
  }






/*
上下排列图标文字
static Column getIconText(
      IconData icon,
      Color iconColor,
      String label,
      TextStyle textStyle) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: iconColor),
        Container(
          margin: const EdgeInsets.only(top: 8.0),
          child: Text(
            label,
            style: textStyle,
          ),
        ),
      ],
    );
  }
 */
/*
  Column为竖直方向的线程布局,如list列(linearlayout竖向排列)
  如果作为一个大的外层布局时使用:SingleChildScrollView+Column
  注意:SingleChildScrollView和Column中不能使用slivers类,否则报错;
  如果Column内部子widget存在listview这考虑使用customScrollView替代;
  注意:当内部Column嵌套滚动的控件如listView时,需要指定高度(或者使用Expanded填满剩余空间),否则报错
   */
  static Column getLayoutColumn(
      {
        required List<Widget> children,
//shrinkWrap: 该属性将决定列表的长度是否仅包裹其内容的长度。当做为子View嵌套在一个无限长的容器组件中时，shrinkWrap必须为true，否则Flutter会给出警告；
        bool shrinkWrap=false,
//max:各个items尽可能多的占用垂直上的位置
        MainAxisSize mainAxisSize=MainAxisSize.max,
//上下对齐方式
        MainAxisAlignment mainAxisAlignment=MainAxisAlignment.start,
//左右对齐方式: 居中center
        CrossAxisAlignment crossAxisAlignment =CrossAxisAlignment.start,
      }
      ) {


    if(shrinkWrap){
      mainAxisSize=MainAxisSize.min;
    }

    return Column(
      mainAxisAlignment:mainAxisAlignment,
      mainAxisSize: mainAxisSize,
      crossAxisAlignment:crossAxisAlignment,
      children: children,
    );
  }

/*
  Flex弹性布局,用法类似android的LinearLayout的layout_weight
  item为Expanded,使用flex属性设置占比
   */
  static Flex getLayoutFlex(
      {
        required List<Expanded> children,
//横向
        Axis direction=Axis.horizontal,
      }
      ){
    return Flex(
        direction:direction,
        children:children
    );
  }

/*
  自动占满剩余横向或竖向可用空间的布局, 用于:Row,Column,Flex内
   */
  static Expanded getLayoutExpanded(
      Widget child,
      {
        int flex=1,
      }
      ){
    return Expanded(
      flex: flex,
      child: child,
    );
  }

  //占满剩余空间
  static Spacer getLayoutSpacer(){
    return const Spacer();
  }

  /*
  是Expanded的父类,多个按比例缩放,
  Flexible和Expanded都是用于在Flutter中调整子部件的尺寸以适应可用空间的小部件。它们之间的区别如下：
  Flexible：Flexible小部件是Flex系列小部件的一部分，在Row、Column或Flex等父级小部件中使用。Flexible小部件允许子部件根据可用空间进行伸缩，并根据flex因子确定伸缩比例。flex因子定义了相对于其他Flexible小部件的伸缩比例。
  Expanded：Expanded小部件是Flexible小部件的一个特定形式，用于在Row、Column或Flex等父级小部件中自动填充剩余可用空间。与Flexible不同，Expanded将flex属性设置为默认值1，使其填充任何剩余的可用空间。如果有多个Expanded小部件并且它们具有相同的flex值，则它们平均分配剩余空间。
  总结来说，Flexible是一个更通用的小部件，允许子部件按照指定的比例进行伸缩，而Expanded则是Flexible的特殊情况，用于自动填充剩余的可用空间。
   */
  static Flexible getLayoutFlexible(
      {
        required int flex,
        required Widget child,
      }
      ){
    return Flexible(flex:flex, child: child);
  }


/*
  流式布局:
  Wrap({
    Key key,
    this.direction = Axis.horizontal,   //排列方向，默认水平方向排列
    this.alignment = WrapAlignment.start,  //子控件在主轴上的对齐方式
    this.spacing = 0.0,  //主轴上子控件中间的间距
    this.runAlignment = WrapAlignment.start,  //子控件在交叉轴上的对齐方式
    this.runSpacing = 0.0,  //交叉轴上子控件之间的间距
    this.crossAxisAlignment = WrapCrossAlignment.start,   //交叉轴上子控件的对齐方式
    this.textDirection,   //textDirection水平方向上子控件的起始位置
    this.verticalDirection = VerticalDirection.down,  //垂直方向上子控件的其实位置
    List<Widget> children = const <Widget>[],   //要显示的子控件集合
  })
  另外一个Flow
  https://www.jianshu.com/p/83f4da35da17
  https://zhuanlan.zhihu.com/p/360229936
  https://blog.csdn.net/chuyouyinghe/article/details/120050050

  区别:
  Wrap一般用于替代Row,当一行显示不全的时候,会自动换行,能满足大部分需求;
  Flow:性能好,更灵活,主要用于一些需要自定义布局策略或性能要求高的场景,自定义每个item的位置(需要自己计算);
   */
  static Wrap getLayoutWrap(
      {
        List<Widget>? itemWidgets,//二选一
        List<dynamic>? itemData,//二选一
        XFunctionWidget? listItemWidgetFunction,//当itemData不为空时,使用
      }

      ){
    if(itemWidgets!=null){
      return Wrap(
        children: itemWidgets,
      );
    }else{
      return Wrap(
        children: itemData!.map((dynamic item) {
          return listItemWidgetFunction!(item);
        }).toList(),
      );
    }
  }


/*
  如果child为空,则产生一个宽为left+right,高为top+bottom的区域
   */
  static Padding getLayoutPadding(
      {
        required EdgeInsets padding,//getEdgeInsets(left,top,right,bottom)
        Widget? child
      }
      ){
    return Padding(
      padding: padding,
      child: child,
    );
  }

//带动画效果的Padding
  static AnimatedPadding getLayoutAnimatedPadding(
      EdgeInsets padding,//getEdgeInsets(left,top,right,bottom)
      Duration duration, //动画时间Duration(milliseconds: 1000)
      Widget child,
      {void Function()? onEndCallback} //动画结束回调  () {print("动画结束时的回调");}
      ){
    return AnimatedPadding(
      padding: padding,
      duration: duration,
      curve: Curves.easeInOut,
      onEnd: onEndCallback,
      child: child,
    );
  }


//对齐适应大小布局////////////////////////////////////////////////////////////////
/*
  FittedBox 对齐+适应(缩放) 会在自己的尺寸范围内调整child尺寸使其适合大小,
  一般需要在其外层再套一个可以设定大小的布局以确定FittedBox自己的大小
  使用情景,类似android的imageView
   */
  static FittedBox getLayoutFittedBox(
      Widget child,
      {
/*
        缩放方式:
        contain: child保持其宽高比,在FittedBox范围内尽可能的大，宽度或者高度达到布局边界范围最大值时，就会停止缩放,不超出其尺寸;
         none: 不缩放,超出范围默认截取;
         fill: child不按原比例缩放填满布局大小;
         cover: child按原大小填满布局,不缩放, 超过范围则截取;
        fitWidth:  child按原比例缩放,填满布局宽度最大值, 高度超出则截取;
        fitHeight: child按原比例缩放,填满布局高度最大值, 宽度超出则截取;
         */
        BoxFit fit=BoxFit.contain,
//对齐方式: topLeft...
        Alignment alignment=Alignment.center,
      }
      ){
    return FittedBox(
      fit: fit, //缩放方式
      alignment: alignment,//对齐方式
      child: child,
    );
  }

/*
  AspectRatio适用于需要固定宽高比的情景,可设置宽度比例, Container(设定width或height)+AspectRatio
  一般需要在其外层再套一个可以设定宽高的布局以确定AspectRatio自己的大小
   */
  static AspectRatio getLayoutAspectRatio(
      Widget child,
      double aspectRatio //宽高比,必须>0,不能为null,必须有限大小
      ){

    return AspectRatio(
      aspectRatio: aspectRatio,
      child: child,
    );

  }

//SizedBox：用于给子元素指定固定的宽高 (放大或缩小其他控件, 也可用Container替代),有可作为space
  static SizedBox getLayoutSizedBox(
      Widget child,
      {
        double? width, //注意:不能设置占满double.infinity,否则报错
        double? height,//注意:不能设置占满double.infinity,否则报错
      }
      ){
    return SizedBox(
      width: width==null?width:width*XTempData.deviceScreenScale,
      height: height==null?height:height*XTempData.deviceScreenScale,
      child: child,
    );
  }

//约束限制大小布局////////////////////////////////////////////////////////////////

  //限制宽度或高度
  static Widget getLayoutLimitedBox(
      {
        required Widget child,
        double maxWidth=double.infinity,
        double maxHeight=double.infinity,
      }
      ){
    return LimitedBox(
      maxWidth:maxWidth,
      maxHeight:maxHeight,
      child:child,
    );
  }


/*
  1.ConstrainedBox 用于对子组件添加额外的约束,可设置尺寸的最大值和最小值。例如，如果你想让子组件的最小高度是 80 像素
  2.SizedBox：用于给子元素指定固定的宽高
  3.UnconstrainedBox：不会对子组件产生任何限制，它允许其子组件按照其本身大小绘制,一般用来去掉父约束
   */
  static ConstrainedBox getLayoutConstrainedBox(
      {
        required Widget child,
        double minWidth=0.0,
        double maxWidth= double.infinity,
        double minHeight=0.0,
        double maxHeight= double.infinity,
      }
      ){
    return ConstrainedBox(
      constraints: BoxConstraints(
        minWidth:minWidth,
        maxWidth:maxWidth,
        minHeight:minHeight,
        maxHeight:maxHeight,
      ),
      child: child,
    );
  }




//UnconstrainedBox：不会对子组件产生任何限制，它允许其子组件按照其本身大小绘制,一般用来去掉父约束
  static UnconstrainedBox getLayoutUnconstrainedBox(
      {
        required Widget child,
      }
      ){
    return UnconstrainedBox(
      child: child,
    );
  }




//卡片
  static Card getLayoutCard(
      {
        required Widget child,
        Color? backgroundColor,//背景色
        Color? shadowColor,//阴影颜色
        double? elevation, //阴影高度
        ShapeBorder? shape, //形状,RoundedRectangleBorder,CircleBorder
        EdgeInsets? margin,//外边距
      }
      ){
    return Card(
      color: backgroundColor,
      surfaceTintColor:Colors.white,
      shadowColor: shadowColor,
      elevation: elevation,
      shape: shape,
      margin: margin,
      child: child,
    );
  }

/*
  显示/隐藏(不推荐,用Visibility替代)
  Offstage: 隐藏不会占位，显示隐藏widget都会加载
  Offstage不能保存组件的状态，组件重新加载,当Offstage不可见时，如果child有动画等，需要手动停掉，Offstage并不会停掉动画等操作
   */
  static Offstage getLayoutOffstage(
      {
        required Widget child,
        bool isVisible=true,
      }
      ){
    return Offstage(
      offstage: isVisible,
      child: child,
    );
  }

/*
  显示/隐藏(推荐)
  Visibility: 隐藏可选择是否占位; 如不占位隐藏时Widget不会加载，如占位，显示/隐藏Widget都会加载
   */
  static Visibility getLayoutVisibility(
      {
        required Widget child,
        bool isVisible=true,
        bool isPlaceHolder=false,//是否占位
      }
      ){
    return Visibility(
      visible: isVisible,
      //隐藏需要占位，前俩个maintainState和maintainSize需要为true,不需要时都为false,
      maintainState: isPlaceHolder,
      maintainSize: isPlaceHolder,
      maintainAnimation: isPlaceHolder, //注意:值同报错maintainState和maintainSize,否则报错
      child: child,
    );
  }


//相对定位布局////////////////////////////////////////////////////////////////


//居中(Container(width:double.infinity)+Center) 等同Align的 Alignment.center
  static Center getLayoutCenter(
      Widget child
      ){
    return Center(
      child:child,
//widthFactor: 1,//宽度缩放因子,如果设置为2,则Align的宽度是child的2倍
//heightFactor: 1,//高度缩放因子,如果设置为2,则Align的高度是child的2倍
    );
  }

  static Align getLayoutAlign(
      {
        Widget? child,
        Alignment alignment=Alignment.topLeft,//对齐方式
      }
      ){
    return Align(
      alignment: alignment,
      child: child,
//widthFactor: 1,//宽度缩放因子,如果设置为2,则Align的宽度是child的2倍
//heightFactor: 1,//高度缩放因子,如果设置为2,则Align的高度是child的2倍
    );
  }


//绝对定位布局////////////////////////////////////////////////////////////////
  /*
  IndexedStack是Stack的子类，Stack是将所有的子组件叠加显示，而IndexedStack只显示指定的子组件
  用于切换子widget, 切换子widget不会重新创建
   */
  static IndexedStack getLayoutIndexedStack(
      {
        required int index,
        required List<Widget> children,
      }
      ){
    return IndexedStack(
      index:index,
      children:children,
    );
  }

/*
  Stack层叠布局(类似Android的Frame布局,也是绝对位置布局)，子组件可以堆叠;
  Stack组件时Flutter中用来进行绝对布局的一个容器组件(Stack组件可以将子组件叠加显示，根据子组件的顺利依次向上叠加)
Positioned组件通常会作为Stack组件的子组件使用，可以设置绝对的位置和尺寸
  结合Positioned绝对定位布局使用（根据Stack的四个角定位）.
  Stack(
            children: [
              Positioned(
                left: 0,
                top: 10,
                child: XWidgetUtils.getWidgetText('hello world hello worldhello worldhello worldhello worldhello worldhello worldhello world'),
              ),
              Positioned(
                left: 20,
                top: 10,
                child: XWidgetUtils.getWidgetText('我要忘了你的样子我要忘了你的样子我要忘了你的样子我要忘了你的样子我要忘了你的样子'),
              )
            ],
          )

   注意:
   如果使用Positioned,则根据Positioned的(left,right,top,bottom)决定位置,
   如果没有使用Positioned的其他布局组件,则按照Stack的alignment决定位置stack

   也可用于实现android的popwindow??
   */
  static Stack getLayoutStack(
      {
        required List<Widget> children,
//此参数用于没有使用Positioned的其他布局组件定位。
        AlignmentDirectional alignment=AlignmentDirectional.topStart,
//没有使用Positioned的子widget如何去适应Stack的大小。
// StackFit.loose表示使用子widget的大小，
// StackFit.expand表示扩伸到Stack的大小。
        StackFit fit=StackFit.loose,
      }
      ){
    return Stack(
      alignment: alignment,
      textDirection: TextDirection.ltr,
//没有使用Positioned的子widget如何去适应Stack的大小。
// StackFit.loose表示使用子widget的大小，
// StackFit.expand表示扩伸到Stack的大小。
      fit: fit,
//超出Stack显示空间的子widget，值为hardEdge时，超出部分会被剪裁（隐藏）
      clipBehavior: Clip.hardEdge,
      children: children,
    );
  }

/*
  绝对定位布局,集合Stack布局使用
  Positioned参数:
  left、top 、right、 bottom分别代表离Stack左、上、右、底四边的距离。
  width和height用于指定定位元素的宽度和高度，
  注意，此处的width、height 和其它地方的意义稍微有点区别，此处用于配合left、top 、right、 bottom来定位widget，
  例:在水平方向时，你只能指定left、right、width三个属性中的两个，如指定left和width后，right会自动算出(left+width)，
  如果同时指定三个属性则会报错，垂直方向同理。
   */
  static Positioned getLayoutPositioned(
      {
        double? left,
        double? top,
        double? right,
        double? bottom,
        double? width,
        double? height,
        required Widget child,
      }

      ){
    return Positioned(
      left: left==null?left:left*XTempData.deviceScreenScale,
      top: top==null?top:top*XTempData.deviceScreenScale,
      right: right==null?right:right*XTempData.deviceScreenScale,
      bottom: bottom==null?bottom:bottom*XTempData.deviceScreenScale,
      width: width==null?width:width*XTempData.deviceScreenScale,
      height: height==null?height:height*XTempData.deviceScreenScale,
      child: child,
    );
  }


  /*
  下拉刷新RefreshIndicator
  其他:
  https://pub.flutter-io.cn/packages/liquid_pull_to_refresh
  https://pub.flutter-io.cn/packages/pull_to_refresh

  注意:内部要直接嵌套带有scroll的控件,如listview,scrollview等,且设置physics=const AlwaysScrollableScrollPhysics(),
  如果存在NotificationListener滚动监听,可能存在冲突,不要RefreshIndicator包裹NotificationListener,而要NotificationListener包裹RefreshIndicator
   */
  static RefreshIndicator getLayoutRefreshIndicator(
      {
        required Widget child,//直接带有scroll的控件
        required RefreshCallback onRefresh,
        Color? color,
      }
      ){
    return RefreshIndicator(
      onRefresh: onRefresh,
      color: color,
      // semanticsLabel:semanticsLabel, //实测:界面没显示
      // semanticsValue:semanticsValue,//实测:界面没显示
      child: child,
    );
  }




  /*
  Listener( //Listener也是Widget
  child: ...,   //省略需要添加触控事件的Widget
  onPointerDown: (PointerDownEvent event) { //手指按下时触发
    ... //此处处理按下逻辑
  },
  onPointerMove: (PointerMoveEvent event) { //手指移动时触发
    ... //此处处理移动逻辑
  },
  onPointerUp: (PointerUpEvent event) { //手指抬起时触发
    ... //此处处理抬起逻辑
  }
);
double orientation = event.orientation;  //移动方向
double localDx = event.localPosition.dx; //相对本身布局坐标的x轴偏移
double localDy = event.localPosition.dy; //相对本身布局坐标的y轴偏移
double posDx = event.position.dx;        //相对全局坐标的x轴偏移
double posDy = event.position.dy;        //相对全局坐标的y轴偏移
double dx = event.delta.dx;              //x轴移动距离
double dy = event.delta.dy;              //y轴移动距离


GestureDetector(
  child: ...,  //省略需要添加触控事件的Widget
  onTap: () { //点击时触发
  },
  onDoubleTap: () { //双击时触发
  },
  onLongPress: () { //长按时触发
  },
  onPanDown: (DragDownDetails event) { //手指按下时触发
    double absoluteX = event.globalPosition.dx;  //按下的位置相对于屏幕原点(左上角)的x轴偏移量
    double absoluteY = event.globalPosition.dy;  //按下的位置相对于屏幕原点(左上角)的y轴偏移量
  },
  onPanUpdate: (DragUpdateDetails event) { //手指滑动时触发
    double distanceX = event.delta.dx;  //从按下到单次滑动结束的x轴距离
    double distanceY = event.delta.dy;  //从按下到单次滑动结束的y轴距离
  },
  onPanEnd: (DragEndDetails event){ //手指滑动结束时触发
    double velocityX = event.velocity.pixelsPerSecond.dx;  //抬起手指时的x轴滑动速度
    double velocityY = event.velocity.pixelsPerSecond.dx;  //抬起手指时的y轴滑动速度
  },
  onScaleUpdate: (ScaleUpdateDetails details) { //两个手指缩放时触发，不能与其他方法同时监听，会报错
      double scale = details.scale.clamp(0.8, 10);   //设置缩放上下限，前个参数为下限，后个参数为上限
  },
);
double absoluteX = DragDownDetails.globalPosition.dx;  //onPanDown方法，按下的位置相对于屏幕原点(左上角)的x轴
偏移量
double absoluteY = DragDownDetails.globalPosition.dy;  //onPanDown方法，按下的位置相对于屏幕原点(左上角)的y轴
偏移量
double distanceX = DragUpdateDetails.delta.dx;  //onPanUpdate方法，从按下到单次滑动结束的x轴距离
double distanceY = DragUpdateDetails.delta.dy;  //onPanUpdate方法，从按下到单次滑动结束的y轴距离
double velocityX = DragEndDetails.velocity.pixelsPerSecond.dx;  //onPanEnd方法，抬起手指时的x轴滑动速度
double velocityY = DragEndDetails.velocity.pixelsPerSecond.dx;  //onPanEnd方法，抬起手指时的y轴滑动速度
double 缩放倍数 = details.scale.clamp(缩小下限值, 放大上限值);   //设置缩放上下限


自定义通知监听
NotificationListener<CustomNotification>( //监听自定义通知，NotificationListener也是Widget
  onNotification: (notification) {
    String message = notification.message; //获取自定义通知中的自定义属性
    ...
  },
  child: ...,  //监听此Widget的通知事件
)
...
CustomNotification("通知内容").dispatch(context); //发送自定义通知


scrollController也可以addListener(),
不要忘记在部件销毁时移除监听器，以避免内存泄漏。您可以在dispose()方法中移除监听器
  @override
void dispose() {
  _scrollController.dispose();
  super.dispose();
}

   */

  /*
  NotificationListener 滚动监听
  注意: 与RefreshIndicator可能冲突, 不能RefreshIndicator包裹NotificationListener, 要NotificationListener包裹RefreshIndicator
  注意:需要做防抖处理,NotificationListener不带防抖
   */
  static NotificationListener getWidgetNotificationListener(
      {
        required Widget child,
        NotificationListenerCallback<Notification>? onNotification,
      }
      ){
    /*
      (Notification notification){
        /*
        ScrollStartNotification开始滚动、
        ScrollUpdateNotification滚动中、
        ScrollEndNotification停止滚动、
        OverscrollNotification滚到边界
         */

        return false; //true时终止向上通知，false时继续向上通知
      }
       */
    return NotificationListener(
      onNotification: onNotification,
      child: child,
    );
  }

  /*
    避免内部滑动与外部滑动冲突(同方向滑动时)
    外部:
    primary: true,
    physics: const ClampingScrollPhysics(),
    内部:
    physics: const NeverScrollableScrollPhysics(),

    //primary: ,//这是否是与父控件PrimaryScrollController相关联的主滚动视图。
    //physics 滑动效果控制:
    BouncingScrollPhysics  //iOS下弹性效果
    ClampingScrollPhysics  //Android下微光效果
    AlwaysScrollableScrollPhysics
    NeverScrollableScrollPhysics //不滚动

     */


  /*
  SingleChildScrollView 类似android的scrollView
  SingleChildScrollView+Column
  注意:SingleChildScrollView和Column中不能使用slivers类,否则报错
  嵌套Listview和gridView时,
  shrinkWrap: true,
  physics: NeverScrollableScrollPhysics(),

  解决:刷新界面停止当前位置
  late ScrollController scrollController;
  Widget onBuildWidget(){
  scrollController=ScrollController(initialScrollOffset: offset);
    return getLayoutSingleChildScrollView(
      child:...,
      scrollController:scrollController,
      onNotification: (Notification notification){
        if(notification is ScrollEndNotification){
          //停止滚动,更新当前位置
          offset=scrollController.offset;
          return true;
        }
        return false;
      }
    );
  }

   */
  static Widget getLayoutSingleChildScrollView(
      {
        required Widget child,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        XFunctionEventNotify? onNotifyScrollChangePosition,//滚动位置改变通知
        XFunctionEventNotify<ScrollController>? onNotifyScrollStop,//停止滚动通知,更新外界传入的scrollview的初始化位置
        XFunctionEventNotify? onNotifyScrollBottomPosition,//注意:这里没有做防抖处理,参考在加载的时候判断做
        NotificationListenerCallback<Notification>? onScrollNotification,//注意要做防抖,参考x_page_state_listview.dart
        RefreshCallback? onRefresh,//下拉刷新
        Axis scrollDirection=Axis.vertical,
        EdgeInsets? padding, //内边距
        bool? primary,
        ScrollPhysics? physics=const AlwaysScrollableScrollPhysics(),
        ScrollController? scrollController,
      }
      ){

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    Widget singleChildScrollView= SingleChildScrollView(
      //key: UniqueKey(),
      scrollDirection:scrollDirection,
      primary:primary,//const BouncingScrollPhysics(), //嵌套iOS拨轮时使用
      physics:physics,
      padding: padding,
      controller: scrollController,
      child: child,
    );



    Widget mWidget;

    bool isEnableNotificationListener=false;
    if(onScrollNotification!=null || onNotifyScrollStop!=null || onNotifyScrollBottomPosition!=null||onNotifyScrollChangePosition!=null){
      isEnableNotificationListener=true;
    }

    bool isEnableRefreshIndicator=false;
    if(onRefresh!=null){
      isEnableRefreshIndicator=true;
    }

    if(isEnableNotificationListener){

      mWidget=getWidgetNotificationListener(
        child: onRefresh!=null?getLayoutRefreshIndicator(
            child: singleChildScrollView,
            onRefresh: onRefresh
        ):singleChildScrollView,
        onNotification:onScrollNotification??(Notification notification){

          if (notification is ScrollUpdateNotification) {
            // 同步滚动位置
            //controller2.jumpTo(notification.metrics.pixels);
            if(onNotifyScrollChangePosition!=null){
              onNotifyScrollChangePosition(notification.metrics.pixels);
            }

          }else if(notification is ScrollEndNotification){
            //监听停止滚动
            if(onNotifyScrollStop!=null){
              //XLogUtils.printLog('---停止滚动');
              //适用更新当前位置: currentOffset=scrollController.offset;
              onNotifyScrollStop(scrollController);
            }

            //监听滚动到底部(适用item占满这个listview情况,否则滚不到无法激活监听)
            if(notification.metrics.extentAfter == 0){
              //已经滑到底部
              if(onNotifyScrollBottomPosition!=null){
                onNotifyScrollBottomPosition(null);
              }
            }

            return true;
          }
          return false;
        },
      );

    }else if(!isEnableNotificationListener && isEnableRefreshIndicator){
      //不开启滚动监听,开启下拉刷新监听
      mWidget=getLayoutRefreshIndicator(
          child: singleChildScrollView,
          onRefresh: onRefresh!
      );
    }else{
      //不开启滚动监听,不开启下拉刷新监听
      mWidget=singleChildScrollView;
    }

    return mWidget;


  }

/*
  CustomScrollView
  可包括多个listview,gridview等其他widget,
  注意:因为customScrollView的子组件属性是slivers,
  items不能带滚动操作,那么使用gridView要变为SliverGrid(不具备滚动),listview要变成SliverFixedExtentList;
  即所有内部子Widget都需要使用slivers类,普通Widget则需要使用SliverToBoxAdapter包裹
   */
  static Widget getLayoutSliverScrollView(
      {
//shrinkWrap: 该属性将决定列表的长度是否仅包裹其内容的长度。当ListView做为子View嵌套在一个无限长的容器组件中时，shrinkWrap必须为true，否则Flutter会给出警告；
        required List<Widget> children,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        XFunctionEventNotify<ScrollController>? onNotifyScrollStop,//停止滚动通知,更新外界传入的scrollview的初始化位置
        XFunctionEventNotify? onNotifyScrollBottomPosition,//滚动到底部通知
        bool shrinkWrap=false,
        ScrollPhysics? physics=const AlwaysScrollableScrollPhysics(),
        RefreshCallback? onRefresh,//下拉刷新
      }
      ){

    //解决刷新界面停留当前位置
    ScrollController scrollController=ScrollController(initialScrollOffset: initialScrollOffset);
    Widget customScrollView=CustomScrollView(
      //key: UniqueKey(),
      shrinkWrap: shrinkWrap,
      controller: scrollController,
      slivers: children,
      physics: physics,
    );


    Widget mWidget;

    bool isEnableNotificationListener=false;
    if(onNotifyScrollStop!=null || onNotifyScrollBottomPosition!=null){
      isEnableNotificationListener=true;
    }

    bool isEnableRefreshIndicator=false;
    if(onRefresh!=null){
      isEnableRefreshIndicator=true;
    }

    if(isEnableNotificationListener){

      mWidget=getWidgetNotificationListener(
        child: onRefresh!=null?getLayoutRefreshIndicator(
            child: customScrollView,
            onRefresh: onRefresh
        ):customScrollView,
        onNotification:(Notification notification){
          if(notification is ScrollEndNotification){
            //监听停止滚动
            if(onNotifyScrollStop!=null){
              //XLogUtils.printLog('---停止滚动');
              //适用更新当前位置: currentOffset=scrollController.offset;
              onNotifyScrollStop(scrollController);
            }

            //监听滚动到底部(适用item占满这个listview情况,否则滚不到无法激活监听)
            if(notification.metrics.extentAfter == 0){
              //已经滑到底部
              if(onNotifyScrollBottomPosition!=null){
                onNotifyScrollBottomPosition(null);
              }
            }

            return true;
          }
          return false;
        },
      );

    }else if(!isEnableNotificationListener && isEnableRefreshIndicator){
      //不开启滚动监听,开启下拉刷新监听
      mWidget=getLayoutRefreshIndicator(
          child: customScrollView,
          onRefresh: onRefresh!
      );
    }else{
      //不开启滚动监听,不开启下拉刷新监听
      mWidget=customScrollView;
    }

    return mWidget;

  }


//widget/////////////////////////////////////////////

  //在CustomScrollView使用,用于包裹普通Widget,注意:不能再包裹Sliver子类
  static SliverToBoxAdapter getWidgetSliverToBoxAdapter(
      Widget child
      )
  {
    return SliverToBoxAdapter(child: child);
  }


  //不带滚动的gridview,在CustomScrollView使用,但不能嵌套在SliverToBoxAdapter中,
  static Widget getWidgetSliverGridView(
      {
        required int crossAxisCount, //列数
        required List<dynamic> listData,
        required XFunctionListItemWidget listItemWidgetFunction,
//子view的宽高比,通过它跳转gridview每行高度(注意:gridview不会根据内容调节高度)
        double childAspectRatio=1.0,
      }
      ){
    return SliverGrid(
      delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index){
          return listItemWidgetFunction(listData[index],index);
        },
        childCount: listData.length,
      ),
//SliverGridDelegateWithFixedCrossAxisCount //适用设置每个item最大宽度,实际屏幕宽度能放多少就多少
//SliverGridDelegateWithMaxCrossAxisExtent // 适用设置每行item固定数量情况
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        childAspectRatio: childAspectRatio,
        crossAxisCount: crossAxisCount,
      ),
    );
  }

  /*
  不带滚动的listview,在CustomScrollView使用
  SliverFixedExtentList(itemExtent: itemExtent,//设置每个item的固定高度)

   */
  static Widget getWidgetSliverListView(
      {
        required List<dynamic> listData,
        required XFunctionListItemWidget listItemWidgetFunction,
      }
      ){
    return SliverList(
//SliverChildBuilderDelegate
//SliverChildListDelegate
      delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index){
          return listItemWidgetFunction(listData[index],index);
        },
        childCount: listData.length,
      ),
      //
    );


  }



  //滑动到列表最后
  static void scrollToListViewScrollViewEnd(
      {
        required ScrollController? scrollController,
        bool isAnimate=false,
        int durationMilliseconds=500,
      }
      ){

    double maxScrollExtent=scrollController?.position.maxScrollExtent??0;
    //由于xScrollController?.position.maxScrollExtent不准确,因此放大2倍,保证滚动到最后
    maxScrollExtent=maxScrollExtent*2;

    if(isAnimate){
      scrollController?.animateTo(
          maxScrollExtent,
          duration: Duration(milliseconds:durationMilliseconds
          ),
          curve: Curves.fastEaseInToSlowEaseOut
      );
    }else{
      scrollController?.jumpTo(maxScrollExtent);
    }
  }

/*
  ListView列表
  ListView(
        padding: EdgeInsets.symmetric(horizontal: 5),
        children: data.map((color) => Container(
                  alignment: Alignment.center,
                  width: 100,
                  height: 50,
                  color: color,
                  child: Text(
                    colorString(color),
                    style: TextStyle(color: Colors.white, shadows: [
                      Shadow(
                          color: Colors.black,
                          offset: Offset(.5, .5),
                          blurRadius: 2)
                    ]),
                  ),
                ))
            .toList(),
      )

      //当作为外部滚动的子widget时,则设置为true
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),

      注意:当使用SingleChildScrollView+ListView(shrinkWrap:true)时,可替代Silver,

      也可以CustomScrollView+SliverToBoxAdapter+ListView(shrinkWrap:true)
      SliverToBoxAdapter(
      child: ListView(
        shrinkWrap: true,
        physics: NeverScrollableScrollPhysics(),
        children: [
          YourChildWidget(),
        ],
      ),
    )

      //带分隔线的listview
      ListView.separated(itemBuilder: itemBuilder, separatorBuilder: separatorBuilder, itemCount: itemCount)
   */
  static Widget getWidgetListView(
      {
        required List<dynamic> listData,
        required IndexedWidgetBuilder listItemWidgetFunction,
        Axis scrollDirection=Axis.vertical,//滚动方向
        EdgeInsets? padding, //内边距
        //shrinkWrap:当做为子View嵌套在一个无限长的容器组件中时，shrinkWrap必须为true(否则Flutter会给出警告),平铺各个item不滚动；
        bool shrinkWrap=false,
        ScrollPhysics? physics,
        double? itemExtent,//设置每条item的固定高度，有助于提高列表的渲染速度
        ScrollController? scrollController,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        RefreshCallback? onRefresh,//下拉刷新
        XFunctionEventNotify<ScrollController>? onNotifyScrollStop,//停止滚动通知,更新外界传入的scrollview的初始化位置
        XFunctionEventNotify? onNotifyScrollBottomPosition,//注意:这里没有做防抖处理,参考在加载的时候判断做
        NotificationListenerCallback<Notification>? onScrollNotification,//注意要做防抖,参考x_page_state_listview.dart
        Color? backgroundColor,
      }
      ){

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    //监听位置
    // scrollController.addListener(() {
    //   //已经滑到底部, 没有防抖处理
    //   // if(scrollController.position.pixels == scrollController.position.maxScrollExtent){
    //   //   if(onNotifyScrollBottomPosition!=null){
    //   //     onNotifyScrollBottomPosition(null);
    //   //   }
    //   // }
    //
    //   // final double maxScrollExtent = scrollController.position.maxScrollExtent;
    //   // final double currentPosition = scrollController.position.pixels;
    //   // final double extentInside = scrollController.position.extentInside;
    //   // XLogUtils.printLog('---maxScrollExtent=$maxScrollExtent,currentPosition=$currentPosition,extentInside=$extentInside');
    //   // if (currentPosition + extentInside >= maxScrollExtent) {
    //   //   // 已滚动到最后
    //   //   if(onNotifyScrollBottomPosition!=null){
    //   //     onNotifyScrollBottomPosition(null);
    //   //   }
    //   // }
    //
    // });

    ListView listView= ListView.builder(
      controller: scrollController,
      scrollDirection: scrollDirection,
      padding: padding??getEdgeInsets(0, 0, 0, 0), //如果不设置,当shrinkWrap=true时,头部会有默认间距
      itemBuilder: listItemWidgetFunction,
      itemCount: listData.length,//如果不填写,这是无限的
      shrinkWrap: shrinkWrap,
      physics: physics ?? (shrinkWrap?const NeverScrollableScrollPhysics():const AlwaysScrollableScrollPhysics()),
      itemExtent: itemExtent==null?itemExtent:itemExtent*XTempData.deviceScreenScale,//设置每条item的固定高度，有助于提高列表的渲染速度
//keyboardDismissBehavior: 键盘关闭模式
    );

    Widget mWidget;

    bool isEnableScrollNotificationListener=false;
    if(onScrollNotification!=null || onNotifyScrollStop!=null || onNotifyScrollBottomPosition!=null){
      isEnableScrollNotificationListener=true;
    }

    bool isEnableRefreshIndicator=false;
    if(onRefresh!=null){
      isEnableRefreshIndicator=true;
    }

    if(isEnableScrollNotificationListener){

      mWidget=getWidgetNotificationListener(
        child: isEnableRefreshIndicator?getLayoutRefreshIndicator(
            child: listView,
            onRefresh: onRefresh!
        ):listView,
        onNotification:onScrollNotification ?? (Notification notification){
          //注意:这里没有防抖处理
          if(notification is ScrollEndNotification){
            //监听停止滚动
            if(onNotifyScrollStop!=null){
              //XLogUtils.printLog('---停止滚动');
              //适用更新当前位置: currentOffset=scrollController.offset;
              onNotifyScrollStop(scrollController);
            }

            //监听滚动到底部(适用item占满这个listview情况,否则滚不到无法激活监听)
            if(notification.metrics.extentAfter == 0){
              //已经滑到底部
              if(onNotifyScrollBottomPosition!=null){
                onNotifyScrollBottomPosition(null);
              }
            }

            return true;
          }
          return false;
        },
      );

    }else if(!isEnableScrollNotificationListener && isEnableRefreshIndicator){
      //不开启滚动监听,开启下拉刷新监听
      mWidget=getLayoutRefreshIndicator(
          child: listView,
          onRefresh: onRefresh!
      );
    }else{
      //不开启滚动监听,不开启下拉刷新监听
      mWidget=listView;
    }


    return getLayoutContainer(
        backgroundColor: backgroundColor,
        child: mWidget
    );

  }



/*
  动画listview
  实测:不支持Dismissible搭配(Dismissible删除ui后没有方法通知到AnimatedList的item的position改变)
   */
  static AnimatedList getWidgetAnimatedList(
      {
        required GlobalKey<AnimatedListState> key,//在page中初始化传入即可:final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
        required List<dynamic> listData,
        required AnimatedItemBuilder listItemWidgetFunction,
        Axis scrollDirection=Axis.vertical,//滚动方向
        EdgeInsets? padding, //内边距
        ScrollController? scrollController,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
      }
      ) {

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    return AnimatedList(
      key: key,
      initialItemCount: listData.length,
      itemBuilder: listItemWidgetFunction,
      padding: padding,
      scrollDirection: scrollDirection,
      controller:scrollController,
//primary: ,//这是否是与父控件PrimaryScrollController相关联的主滚动视图。
//controller: ,//控制滚动到的位置
//physics 滑动效果控制 ，BouncingScrollPhysics 是列表滑动 iOS 的回弹效果；AlwaysScrollableScrollPhysics 是 列表滑动 Android 的水波纹回弹效果；ClampingScrollPhysics 普通的滑动效果
    );
  }


/*
  拖拽item的listView, 实测:不支持Dismissible搭配(Dismissible删除ui后没有方法通知到ReorderableListView的item的position改变)
  android/iOS 不带默认图标
  mac/linux/window/web 带默认图标()
   */
  static XReorderableListView getWidgetReorderableListView(
      {
        required List<dynamic> listData,
        required IndexedWidgetBuilder listItemWidgetFunction,
        required ReorderCallback onReorder,//拖拽位置变动
        required bool Function(int index) onInterceptorIsListItemCanDrag,//拦截器,item是否能被拖拽
        Axis scrollDirection=Axis.vertical,//滚动方向
        EdgeInsets? padding, //内边距
        ScrollController? scrollController,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
      }
      ) {
// return ReorderableListView.builder(
//   key: UniqueKey(),
//   scrollDirection: scrollDirection,
//   padding: padding,
//   itemBuilder: listItemWidgetFunction,
//   itemCount: listData.length,
//   onReorder:onReorder,//拖拽位置变动
// );

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    return XReorderableListView.builder(
      scrollDirection: scrollDirection,
      scrollController:scrollController,
      padding: padding,
      itemBuilder: listItemWidgetFunction,//itemWidget 必须有key,否则报错
      itemCount: listData.length,
      onReorder:onReorder,
      onInterceptorIsListItemCanDrag: onInterceptorIsListItemCanDrag,//拦截器,item是否能被拖拽
    );
  }


/*
  分组ListView(注意:分组item要是XBeanExpansionItem类型)
  ExpansionPanelList/ExpansionPanel：实现多个展开或1个展开,在item中的isExpanded决定,需要在回调中setState())
  注意事项：
  ExpansionPanelList必须在可滚动组件中，例如SingleChildScrollView
  ExpansionPanel内加入子ListView的时候需要设置shrinkWrap: true不然是无法显示的，因为ExpansionPanel自己也是个高度无法确定的组件
   */
  static Widget getWidgetExpansionPanelList(
      {
        required List<dynamic> listData,
//分组头widget
        required XFunctionGroupItemWidget<dynamic> groupItemWidgetFunction,
//子widget(这里已listview作为子widget,适配多个或1个item)
        required XFunctionListItemWidget<dynamic> listItemWidgetFunction,
//展示隐藏点击回调,需要通知更新数据
// expansionCallback: (int panelIndex, bool isExpanded){
//   setState(() {
//   XBeanExpansionItem item= itemData[panelIndex] as XBeanExpansionItem;
//     item.isExpanded = !isExpanded;
//   });
// },
        required ExpansionPanelCallback expansionCallback,
        Color? backgroundColor,
        ScrollController? scrollController,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        EdgeInsets? padding, //内边距
      }
      ) {

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    return SingleChildScrollView(
      controller: scrollController,
      child: getLayoutContainer(
        backgroundColor: backgroundColor,
        child: ExpansionPanelList(
          expansionCallback:expansionCallback,
          animationDuration: const Duration(milliseconds: 200),
          children: listData.map((dynamic listDataItem){
            XBeanExpansionItem item= listDataItem as XBeanExpansionItem;

            return ExpansionPanel(
              headerBuilder: (BuildContext context, bool isExpanded){
                return groupItemWidgetFunction(item.groupItem,isExpanded);
              },
//这里使用listview,已设置shrinkWrap=true,适配多个或1个子item
              body: getWidgetListView(
                listData: item.itemList,
                padding:padding,
                listItemWidgetFunction: (BuildContext context, int index){
                  return listItemWidgetFunction(item.itemList[index],index);
                },
                shrinkWrap:true,
              ),
              canTapOnHeader: true,
              isExpanded:item.isExpanded,
              backgroundColor: item.backgroundColor,
            );
          }).toList(),
        ),
      ),
    );
  }

/*
  分组ListView(注意:分组item要是XBeanExpansionItem类型)
  ExpansionPanelList.radio/ExpansionPanelRadio：只有1个展开,自动更新ui,不需要在回调中setState()
  ExpansionTile：扩展面板
   */
  static Widget getWidgetExpansionPanelRadioList(
      {
        required List<dynamic> listData,
//分组头widget
        required XFunctionGroupItemWidget<dynamic> groupItemWidgetFunction,
//子widget(这里已listview作为子widget,适配多个或1个item)
        required XFunctionListItemWidget<dynamic> listItemWidgetFunction,
//初始化展开的item,可不设置
        dynamic initialOpenPanelValue,
        Color? backgroundColor,
        ScrollController? scrollController,
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        EdgeInsets? padding, //内边距
      }
      ) {


    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    return SingleChildScrollView(
      controller: scrollController,
      child: getLayoutContainer(
        backgroundColor: backgroundColor,
        child: ExpansionPanelList.radio(
          initialOpenPanelValue: initialOpenPanelValue,
          animationDuration: const Duration(milliseconds: 200),
          children: listData.map((dynamic listDataItem){
            XBeanExpansionItem item= listDataItem as XBeanExpansionItem;

            return ExpansionPanelRadio(
              value: item,
              headerBuilder: (BuildContext context, bool isExpanded){
                return groupItemWidgetFunction(item.groupItem,isExpanded);
              },
//这里使用listview,已设置shrinkWrap=true,适配多个或1个子item
              body: getWidgetListView(
                listData: item.itemList,
                padding:padding,
                listItemWidgetFunction: (BuildContext context, int index){
                  return listItemWidgetFunction(item.itemList[index],index);
                },
                shrinkWrap:true,
              ),
              canTapOnHeader: true,
              backgroundColor: item.backgroundColor,
            );
          }).toList(),
        ),
      ),
    );
  }

  //gridview/////////////////////////
  /*
  https://pub-web.flutter-io.cn/packages/flutter_staggered_grid_view
  https://pub-web.flutter-io.cn/packages/flutter_draggable_gridview
  https://pub-web.flutter-io.cn/packages/flutter_reorderable_grid_view
   */
  /*
  支持拖拽 GraggableGridView
   */
  // Widget getWidgetDraggableGridView(){
  //   return
  // }

/*
  GridView 网格布局
  //自动每行item个数
  return new GridView.extent(
        maxCrossAxisExtent: 150.0,//item的最大宽度,每行能放下多少个item就多少个
        padding: const EdgeInsets.all(4.0),
        children: list.map((var item){
          return showGridViewItem(item);
        }).toList()
    );

    //
    return GridView.count(
      scrollDirection: scrollDirection,
      padding: padding,
      crossAxisCount: crossAxisCount,
      //childAspectRatio: , //设置宽高的比例,GridView的子组件直接设置宽高没有反应，可以通过childAspectRatio修改宽高
      children: children,
    );

    注意:当使用SingleChildScrollView+GridView(shrinkWrap:true)时,可替代Silver
   */
  static Widget getWidgetGridView(
      {
        required int crossAxisCount, //列数
        required List<dynamic> listData,
        required IndexedWidgetBuilder listItemWidgetFunction,
        Axis scrollDirection=Axis.vertical,//滚动方向
        EdgeInsets? padding=EdgeInsets.zero, //内边距,实测:默认不为0,头部有间距
//shrinkWrap: 当做为子View嵌套在一个无限长的容器组件中时，shrinkWrap必须为true，否则Flutter会给出警告,平铺每个item；
        bool shrinkWrap=false,
        double childAspectRatio=1.0,//子view的宽高比,通过它跳转gridview每行高度(注意:gridview不会根据内容调节高度)
        double? childHeight, //子view高度,为空则以childAspectRatio比例为准
        double crossAxisSpacing=0, //子view之间横向间距
        double initialScrollOffset=0.0, //外界传入scrollview的初始化位置,适用ui刷新停留当前位置
        RefreshCallback? onRefresh,//下拉刷新
        XFunctionEventNotify<ScrollController>? onNotifyScrollStop,//停止滚动通知,更新外界传入的scrollview的初始化位置
        XFunctionEventNotify? onNotifyScrollBottomPosition, //注意:这里没有做防抖处理,参考在加载的时候判断做
        NotificationListenerCallback<Notification>? onScrollNotification,//注意要做防抖,参考x_page_state_listview.dart
        Color? backgroundColor,
        ScrollController? scrollController,
      }
      ){

    //解决刷新界面停留当前位置
    scrollController??=ScrollController(initialScrollOffset: initialScrollOffset);

    Widget gridView=GridView.builder(
      controller: scrollController,
      itemCount: listData.length,
      scrollDirection: scrollDirection,
      padding: padding,
      shrinkWrap:shrinkWrap,
      physics:shrinkWrap?const NeverScrollableScrollPhysics():null,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(//网格代理：定交叉轴数目
        crossAxisCount: crossAxisCount,//列数
        childAspectRatio:childAspectRatio, //子view高度比例
        crossAxisSpacing: crossAxisSpacing,
        mainAxisExtent:childHeight==null?childHeight:childHeight*XTempData.deviceScreenScale,//子view高度,mainAxisExtent不为空着以mainAxisExtent高度为准,否则以childAspectRatio比例为准
      ),
      itemBuilder: listItemWidgetFunction,
    );


    Widget mWidget;

    bool isEnableScrollNotificationListener=false;
    if(onScrollNotification!=null || onNotifyScrollStop!=null || onNotifyScrollBottomPosition!=null){
      isEnableScrollNotificationListener=true;
    }

    bool isEnableRefreshIndicator=false;
    if(onRefresh!=null){
      isEnableRefreshIndicator=true;
    }

    if(isEnableScrollNotificationListener){

      mWidget=getWidgetNotificationListener(
        child: isEnableRefreshIndicator?getLayoutRefreshIndicator(
            child: gridView,
            onRefresh: onRefresh!
        ):gridView,
        onNotification:onScrollNotification ?? (Notification notification){
          //注意:这里没有防抖处理
          if(notification is ScrollEndNotification){
            //监听停止滚动
            if(onNotifyScrollStop!=null){
              //XLogUtils.printLog('---停止滚动');
              //适用更新当前位置: currentOffset=scrollController.offset;
              onNotifyScrollStop(scrollController);
            }

            //监听滚动到底部(适用item占满这个listview情况,否则滚不到无法激活监听)
            if(notification.metrics.extentAfter == 0){
              //已经滑到底部
              if(onNotifyScrollBottomPosition!=null){
                onNotifyScrollBottomPosition(null);
              }
            }

            return true;
          }
          return false;
        },
      );

    }else if(!isEnableScrollNotificationListener && isEnableRefreshIndicator){
      //不开启滚动监听,开启下拉刷新监听
      mWidget=getLayoutRefreshIndicator(
          child: gridView,
          onRefresh: onRefresh!
      );
    }else{
      //不开启滚动监听,不开启下拉刷新监听
      mWidget=gridView;
    }

    return getLayoutContainer(
        backgroundColor: backgroundColor,
        child: mWidget
    );


  }

  //分割线 分隔线 (横向)
  static Widget getWidgetDivider(
      {
        Color? color,
        double? height, //默认自动设为设备1像素值
        double marginLeft=10,
        double marginRight=10,
        double marginTop=0,
        double marginBottom=0,
      }
      ) {

    color??=XColorUtils.dividerColor;
    height??=XTempData.defaultDividerHeight;

    if(height<=0){
      height=0.5;
    }

    return getLayoutContainer(
      width: double.infinity,
      height: height,
      margin: getEdgeInsets(marginLeft, marginTop, marginRight, marginBottom),
      child: Divider(
        height: height+0.5,//上下空间
        thickness: height, //厚度
        color: color,//颜色
        indent: 0,//距左边间距
        endIndent: 0,//距右边间距
      )
    );

  }

  //虚线
  static Widget getWidgetDividerDashedLine(
      {
        Color? color,
        double? height,
        double dashWidth=3,
        double dashSpace=6,
        double marginLeft=10,
        double marginRight=10,
        double marginTop=0,
        double marginBottom=0,
        double? width,
      }
      ) {

    color??=XColorUtils.dividerColor;
    height??=XTempData.defaultDividerHeight;
    if(height<=0){
      height=0.5;
    }

    return getLayoutContainer(
      width: double.infinity,
      height: height,
      margin: getEdgeInsets(marginLeft, marginTop, marginRight, marginBottom),
      child: XDividerDashedLine(
        strokeWidth: height, //厚度
        color: color,//颜色
        dashWidth: dashWidth,
        dashSpace: dashSpace,
      ),
    );
  }

  //分割线 分隔线 (竖向)
  static Widget getWidgetDividerVertical(
      {
        Color? color,
        double? width, //默认自动设为设备1像素值
        double height=15,
        double marginLeft=10,
        double marginRight=10,
        double marginTop=0,
        double marginBottom=0,
      }
      ) {

    color??=XColorUtils.dividerColor;
    width??=XTempData.defaultDividerHeight;
    if(width<=0){
      width=0.5;
    }

    return Container(
      margin: getEdgeInsets(marginLeft, marginTop, marginRight, marginBottom),
      width: width,
      height: height, // 竖向分割线高度
      color: color, // 分割线颜色
    );

  }


/*
  Checkbox 多选(勾选)
  onChanged:(bool? value) {
          setState(() {
            isCurrentSelect = value!;
          });
   */
  static Widget getWidgetCheckbox(
      {
        required bool isCurrentSelect,//外部传入
        required ValueChanged<bool?>? onChanged,
        Color? selectColor,//选中颜色
        String? text,
        TextStyle? style,
        double? width,
        double? height,
      }
      ){

    Widget checkboxWidget=getLayoutSizedBox(
        width:width??XTempData.defaultCheckboxWidth,
        height:height??XTempData.defaultCheckboxHeight,
        getLayoutFittedBox(
            Checkbox(
              // materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
              // visualDensity: VisualDensity.compact,
              checkColor: Colors.white,//选中勾选标记的颜色
              activeColor: selectColor??XColorUtils.appMainColor, //选中颜色
              value: isCurrentSelect,
              onChanged: onChanged,
              // shape: RoundedRectangleBorder(
              //   side: const BorderSide(color: XColorUtils.colorF8F8F8, width: 3.0), // 设置边框颜色和宽度
              //   borderRadius: BorderRadius.circular(5.0), // 设置边框圆角
              // ),
            )
        )
    );



    if(XStringUtils.isEmpty(text)){
      return checkboxWidget;
    }else{
      return getLayoutRow(
          crossAxisAlignment:CrossAxisAlignment.center,
          children:[
            checkboxWidget,
            getWidgetText(text!,style: style),
          ]);
    }
  }

//选中和未选颜色
  static Color _getColor(Set<WidgetState> states) {
    const Set<WidgetState> interactiveStates = <WidgetState>{
      WidgetState.pressed,
      WidgetState.hovered,
      WidgetState.focused,
    };
    if (states.any(interactiveStates.contains)) {
      return Colors.blue;
    }
    return Colors.red;
  }

/*
  Radio(单选)
  currentSelectValue在外面传入的值(默认值)

  注意:要在onChanged()方法中,要使用setState更新当前选择值currentSelectValue
  (value){
    setState(() {
                currentSelectValue=value;
              });
    print('$value')
  }

  使用(多个单选):
  XWidgetUtils.getWidgetRadio(
            currentSelectValue: currentSelectValue,
            labelValue: 'a',
            onChanged: (value){
              setState(() {
                currentSelectValue=value;
              });
              XLogUtils.printLog('$value');
            }),
  XWidgetUtils.getWidgetRadio(
            currentSelectValue: currentSelectValue,
            labelValue: 'b',
            onChanged: (value){
              setState(() {
                currentSelectValue=value;
              });
              XLogUtils.printLog('$value');
            }),
   */
  static Widget getWidgetRadio(
      {
        required String? currentSelectValue,//外部传入
        required String labelValue,//此Radio的值, currentSelectValue要在各自的labelValue中
        required ValueChanged<String?>? onChanged,
        required String showText,
        TextStyle? textStyle,
        EdgeInsets? padding,
        double? width,
        double? height,
      }
      )
  {

    Widget radioWidget=getLayoutRow(
        crossAxisAlignment:CrossAxisAlignment.center,
        children:[
          getLayoutSizedBox(
              width:width??XTempData.defaultRadioWidth,
              height:height??XTempData.defaultRadioHeight,
              getLayoutFittedBox(
                  Radio<String>(
                    groupValue: currentSelectValue,
                    value: labelValue,
                    onChanged: onChanged,
                    //fillColor: ,已在全局themes中设置好
                  )
              )
          ),
          getWidgetText(
              labelValue,
              style: textStyle
          ),
        ]
    );


    if(padding!=null){

      return getLayoutPadding(
          padding: padding,
          child: radioWidget
      );

    }else{
      return radioWidget;
    }

  }

/*
  Switch 开关
  currentValue当前值,外面传入
  注意:要在onChanged()方法中,要使用setState更新当前选择值currentSelectValue
  (value){
    setState(() {
                currentValue=value;
              });
    print('$value')

    final MaterialStateProperty<Color?> trackColor =
        MaterialStateProperty.resolveWith<Color?>(
      (Set<MaterialState> states) {
        // Track color when the switch is selected.
        if (states.contains(MaterialState.selected)) {
          return Colors.amber;
        }
        // Otherwise return null to set default track color
        // for remaining states such as when the switch is
        // hovered, focused, or disabled.
        return null;
      },
    );
    final MaterialStateProperty<Color?> overlayColor =
        MaterialStateProperty.resolveWith<Color?>(
      (Set<MaterialState> states) {
        // Material color when switch is selected.
        if (states.contains(MaterialState.selected)) {
          return Colors.amber.withOpacity(0.54);
        }
        // Material color when switch is disabled.
        if (states.contains(MaterialState.disabled)) {
          return Colors.grey.shade400;
        }
        // Otherwise return null to set default material color
        // for remaining states such as when the switch is
        // hovered, or focused.
        return null;
      },
    );
  }
   */
  static Widget getWidgetSwitch(
      {
        required bool currentValue,
        required ValueChanged<bool>? onChanged, //设置为null禁用开关
        double? width,
        double? height,
        //通常全局配置
        WidgetStateProperty<Color?>? overlayColor,//按压时覆盖的颜色 为空则使用全局themes设置
        WidgetStateProperty<Color?>? trackColor,//轨道颜色 为空则使用全局themes设置
        WidgetStateProperty<Color?>? thumbColor,//滑块颜色 为空则使用全局themes设置
      }
      ){


    Switch item=Switch(
      value: currentValue,
      onChanged: onChanged,
      overlayColor: overlayColor,
      trackColor: trackColor,
      thumbColor: thumbColor,
      // activeColor: activeColor,//激活颜色
      // activeTrackColor: activeTrackColor, //激活状态下的滑轨颜色
      // inactiveTrackColor : Colors.black12, //关闭状态的滑轨颜色
      // inactiveThumbColor: Colors.grey,
      // activeThumbImage: AssetImage('images/play.png') , //激活状态的thumb图片
      // inactiveThumbImage : AssetImage('images/pause.png') , //非激活状态的thumb图片
    );

    //通过放大缩小改变Switch大小
    // return Transform.scale(
    //     scale: 2.0,
    //   child: item,
    // );


    return getLayoutSizedBox(
        width:width??XTempData.defaultSwitchWidth,
        height:height??XTempData.defaultSwitchHeight,
        getLayoutFittedBox(item)
    );

  }


/*
  Slider 滑块
   */
  static Slider getWidgetSlider(
      {
        required double currentValue,
        required double min,
        required double max,
        int? divisions,
        required ValueChanged<double>? onChanged,
      }
      ){
    return Slider(
      value: currentValue,
      min: min,
      max: max,
      divisions:divisions,
      onChanged: onChanged,
      label: '$currentValue',
    );
  }

/*
  Stepper 步骤条
  Stepper必须在滑动组中,如SingleChildScrollView

  第三方:
  https://pub.flutter-io.cn/packages/steps_indicator
  https://pub.flutter-io.cn/packages/step_progress_indicator
  https://pub.flutter-io.cn/packages/steps
  https://pub.flutter-io.cn/packages/easy_stepper
  https://pub.flutter-io.cn/packages/im_stepper

  Step 属性	介绍
  title	@required 标题控件
  subtitle	副标题控件
  content	@required 内容控件
  state	当前 step 的状态，StepState 会改变每一个 step 的图标，默认为 StepState.indexed
  isActive	是否激活状态，默认为 false，isActive == true 时会变成蓝色

  Stepper(

          controlsBuilder: (BuildContext context, {VoidCallback? onStepContinue, VoidCallback? onStepCancel}){
            return Row(
              children: [
                TextButton(
                  child: Text('批准',style: tsct,),
                  onPressed: onStepContinue,
                ),
                TextButton(
                  child: Text('截断',style: tsce,),
                  onPressed: onStepCancel,
                ),
              ],
            );
          },
          currentStep: _currentStep,
          onStepContinue: (){
            if(_currentStep<4)
              setState(() {
                stlist[_currentStep]=StepState.complete;
                _currentStep++;
              });
            else
              setState(() {
                stlist[_currentStep]=StepState.complete;
                _currentStep=4;
              });
          },
          steps: [
            Step(
              title: Text('2021-07-24'),
              subtitle: Text('李亚彤老师'),
              state: stlist[0],
              content: Text('使用多媒体教室108'),
            ),
            Step(
                title: Text('2021-07-25'),
                subtitle: Text('张利荣老师'),
                state: stlist[1],
                content: Text('通过')
            ),
          ],
        ),
   */
  static Widget getWidgetStepper(
      {
        required List<Step> steps,
        int currentStep=0,
        ControlsWidgetBuilder? controlsBuilder,
        StepperType type=StepperType.vertical,
        ValueChanged<int>? onStepTapped,
        VoidCallback? onStepContinue,
        VoidCallback? onStepCancel,
        Color? backgroundColor,
      }
      ){
    return SingleChildScrollView(
        child: getLayoutContainer(
            backgroundColor: backgroundColor,
            child: Stepper(
              steps: steps,
              type: type,
              currentStep: currentStep,
              onStepTapped: onStepTapped,//step 点击回调函数(自定义)
              onStepContinue: onStepContinue,//Next 按钮点击回调函数
              onStepCancel: onStepCancel,//Cancel 按钮点击回调函数
              controlsBuilder:controlsBuilder, //控制栏(内容下方按钮构建函数)
//physics 滑动的物理效果
            )
        )
    );

  }



  /*
  圆点
   */
  static Widget getWidgetCircleDot(
      {
        double radius=2.5,//半径
        Color color=const Color(0xFF999999),
        EdgeInsets? margin,
      }
      )
  {
    return Container(
      margin: margin,
      width: radius*2,
      height: radius*2,
      decoration: BoxDecoration(
        color: color,
        shape: BoxShape.circle,
      ),
    );
  }


  /*
  滚筒listview
  ListWheelScrollView(每项显示像卡片一样,像时间,地址选择器)
   */
  static ListWheelScrollView getWidgetListWheelScrollView(
      {
        required List<dynamic> listData,
        required double itemExtent,//设置每个item的固定高度
        required XFunctionWidget listItemWidgetFunction,
        ScrollController? scrollController,
      }
      ){
    return ListWheelScrollView(
      controller: scrollController,
      itemExtent: itemExtent*XTempData.deviceScreenScale,
      children: listData.map((dynamic listDataItem){
        return listItemWidgetFunction(listDataItem);
      }).toList(),
    );
  }

  /*
  拨轮,滑轮
  注意:使用CupertinoPicker需要设置宽度,否则报错
  外套Container可设置宽度和高度,或者使用getLayoutExpanded(child)在row中,自动确认宽度

  跳到指定位置: scrollController.jumpToItem(itemPosition);

   */
  static Widget getWidgetCupertinoPicker(
      {
        Key? key,
        FixedExtentScrollController? scrollController, //CupertinoPicker,设置初始值用于定位
        required List<dynamic> data,
        required XFunctionWidget<dynamic> itemWidgetBuilder, //XWidgetUtils.getWidgetText('第$value季度',style: XStyleUtils.textStyle_000_18(),maxLines: 1,isChineseWordAddCode:false);
        required ValueChanged<int> onSelectedItemChanged, //下标index值
        double height=150,//整体高度,否则等同itemHeight,只显示选中的item高度
        double itemHeight=32,
        EdgeInsets? margin,
        EdgeInsets? padding,
        Color? backgroundColor,
        Color? dividerColor,
        double dividerMarginLeft=0,
        double dividerMarginRight=0,
        bool isUseMagnifier=false,//选中的item是否使用放大
        double magnification=1.0,//放大倍数,如1.2
      }
      ){

    dividerColor??=XColorUtils.dividerColor;


    return getLayoutContainer(
        height:height,
        margin:margin,
        padding:padding,
        //NotificationListener滚动监听
        child: NotificationListener(
          onNotification:(Notification notification){

            //监听停止滚动
            if(notification is ScrollEndNotification
                && notification.metrics is iOS.FixedExtentMetrics
            ){
              //item下标值
              int position=(notification.metrics as iOS.FixedExtentMetrics).itemIndex;
              //XLogUtils.printLog('---NotificationListener,position= $position');

              /*
              实测FixedExtentScrollController不会自动更新初始值,只能手动新建(final不能修改),
              解决外界setState()更新ui时,能重新定位到已选,再onSelectedItemChanged回调中,手动更新:
              scrollController=FixedExtentScrollController(initialItem: position);
               */
              //通知
              onSelectedItemChanged(position);
              return true;
            }

            return false;
          },
          child: iOS.CupertinoPicker(
            key:key,
            backgroundColor: backgroundColor,
            itemExtent: itemHeight*XTempData.deviceScreenScale,
            useMagnifier: isUseMagnifier,
            magnification:magnification,
            scrollController:scrollController,
            onSelectedItemChanged: (int position){
              //实测:不能在这里做联动更新动作,如 setState(),每次经过item都会触发,导致界面重绘大量进行,会卡顿
              //解决办法: 外层嵌套NotificationListener,监听滚动停止,再处理
              //XLogUtils.printLog('---CupertinoPicker,position= $position');
            },
            //浮在选择item上方的widget,上下双线
            selectionOverlay:getLayoutContainer(
                child: getLayoutColumn(
                    mainAxisAlignment:MainAxisAlignment.center,
                    crossAxisAlignment:CrossAxisAlignment.center,
                    children: [
                      getWidgetDivider(color: dividerColor,marginLeft: dividerMarginLeft,marginRight:dividerMarginRight),
                      getLayoutExpanded(getLayoutContainer()),
                      getWidgetDivider(color: dividerColor,marginLeft: dividerMarginLeft,marginRight:dividerMarginRight),
                    ]
                )
            ),
            children: data.map((dynamic item){
              //实测在这里设置item宽度无效,要在外部设置宽度
              return getLayoutCenter(itemWidgetBuilder(item));
            }).toList(),
          ),
        )
    );
  }


//手势,点击事件控件/////////////////////////////////////////////////////////

/*
  原始指针触摸事件,如输入框获得焦点,
  注意:不要用点击事件那个控件
   */
  static Listener getWidgetListener(
      {
        Widget? child,
        PointerDownEventListener? onPointerDown,
        PointerMoveEventListener? onPointerMove,
        PointerUpEventListener? onPointerUp,
        PointerCancelEventListener? onPointerCancel
      }
      ){

    return Listener(
      onPointerDown: onPointerDown,
//手指移动
      onPointerMove: onPointerMove,
//手指抬起
      onPointerUp: onPointerUp,
//触摸取消
      onPointerCancel: onPointerCancel,
      child: child,
    );
  }


/*
GestureDetector 比Listener更强大
https://flutter.cn/docs/development/ui/advanced/gestures
  缩放手势: 可结合OverflowBox控件使用(使用OverflowBox可以让子组件宽高超过父组件)
  onScaleUpdate: (ScaleUpdateDetails e) {
      setState(() {
        //缩放倍数在0.8到10倍之间
        width = 200 * e.scale.clamp(0.8, 10);
      });
    },

  拖动: 结合Positioned使用
  child: GestureDetector(
        onPanDown: (DragDownDetails e) {
          //打印手指按下的位置
          print("手指按下：${e.globalPosition}");
        },
        //手指滑动
        onPanUpdate: (DragUpdateDetails e) {
          setState(() {
            left += e.delta.dx;
            top += e.delta.dy;
          });
        },
        onPanEnd: (DragEndDetails e) {
          //打印滑动结束时在x、y轴上的速度
          print(e.velocity);
        },

        child: Container(
          width: 72,
          height: 72,
          decoration: BoxDecoration(
            color: Colors.blueAccent,
            borderRadius: BorderRadius.circular(36)
          ),
        ),
      ),

      其他手势:
      垂直滑动
      onVerticalDragDown: (details) {},
      onVerticalDragStart: (details) {},
      onVerticalDragUpdate: (details) {},
      onVerticalDragEnd: (details) {},
      onVerticalDragCancel: () {},
      水平滑动
      onHorizontalDragDown: (details) {},
      onHorizontalDragStart: (details) {},
      onHorizontalDragUpdate: (details) {},
      onHorizontalDragEnd: (details) {},
      onHorizontalDragCancel: () {},
   */
  static GestureDetector getWidgetGestureDetector(
      {
        Key? key,
        Widget? child,
        /*
      当behavior选择deferToChild时，只有当前容器中的child被点击时才会响应点击事件；
      当behavior选择opaque时，点击整个区域都会响应点击事件，但是点击事件不可穿透向下传递，注释翻译：阻止视觉上位于其后方的目标接收事件，所以我需要的这种效果直接将behavior设置为HitTestBehavior.opaque就可以了；
      当behavior选择translucent时，同样是点击整个区域都会响应点击事件，和opaque的区别是点击事件是否可以向下传递，注释翻译：半透明目标既可以在其范围内接受事件，也可以允许视觉上位于其后方的目标接收事件。
       */
        HitTestBehavior? behavior,
        GestureTapCallback? onTap, //单击, (){print('单击')}
        GestureTapCallback? onDoubleTap, //双击 ()=> print('双击')
        GestureTapCallback? onLongPress, //长按
        GestureTapCallback? onTapCancel, //取消
        void Function(TapDownDetails details)? onTapDown, //按下
        void Function(TapUpDetails details)? onTapUp, //松开
        void Function(ScaleUpdateDetails details)? onScaleUpdate, //缩放
        void Function(DragDownDetails details)? onPanDown, //拖动-按下位置
        void Function(DragEndDetails details)? onPanEnd, //拖动-结束(松开)位置
        void Function(DragUpdateDetails details)? onPanUpdate, //拖动滑动中
      }
      ){
    return GestureDetector(
      key:key,
      behavior:behavior,
      child: child,
      onTap: onTap,
      onDoubleTap: onDoubleTap,
      onLongPress: onLongPress,
      onTapCancel: onTapCancel,
      onTapDown: onTapDown,
      onTapUp: onTapUp,
      onScaleUpdate: onScaleUpdate,
      onPanDown: onPanDown,
      onPanEnd: onPanEnd,
      onPanUpdate: onPanUpdate,

    );
  }



/*
  Focus 焦点监听控件
  return Focus(
    child: Builder(
      builder: (context) {
        final bool hasPrimary = Focus.of(context).hasPrimaryFocus;
        print('Building with primary focus: $hasPrimary');
        return const SizedBox(width: 100, height: 100);
      },
    ),
  );
   */


//按钮 TextButton,OutlinedButton,ElevatedButton,IconButton//////////////////////////////////////
/*
  注意:按钮的字体样式并不在Text的TextStyle中修改,而是在本身的style使用ButtonStyle修改
  IconButton(
        icon: const Icon(
          Icons.arrow_back_ios_new_rounded,
          color: Color(0xFF333333),
        ),
        onPressed: () {
          //返回上一页
          finishPage();
        }
   */

  /*
  倒数秒数按钮(或文字)
   */
  static Widget getWidgetTimerButton(
      {
        required int timeout_s,
        String? defaultName,
        double? fontSize,
        FontWeight? fontWeight,
        Color fontColor=const Color(0xFFFFFFFF),
        Color defaultBackgroundColor=const Color(0xffff3333),
        Color? timerBackgroundColor,
        double? width,//注意不能为double.infinity
        double? height,//注意不能为double.infinity
        EdgeInsets? margin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? padding,//getEdgeInsets(left,top,right,bottom)
        Color? borderColor,
        double? borderWidth,
        bool isCircle=false,
        double topLeftRadius=5,
        double topRightRadius=5,
        double bottomLeftRadius=5,
        double bottomRightRadius=5,
        bool isAutoStart=false,
        XFunctionEventNotify? onTimerComplete,
        XFunctionEventNotify? onClick,
        String? tag_action_receive_notify,
      }
      )
  {
    return XStatefulWidget(
            ()=>XButtonTimeState(
          timeout_s: timeout_s,
          defaultName: defaultName,
          fontColor: fontColor,
          fontWeight:fontWeight,
          fontSize:fontSize,
          defaultBackgroundColor: defaultBackgroundColor,
          timerBackgroundColor: timerBackgroundColor,
          margin:margin,
          padding:padding,
          width:width,
          height:height,
          borderColor:borderColor,
          borderWidth: borderWidth,
          isCircle:isCircle,
          topLeftRadius:topLeftRadius,
          topRightRadius:topRightRadius,
          bottomLeftRadius:bottomLeftRadius,
          bottomRightRadius:bottomRightRadius,
          isAutoStart:isAutoStart,
          onTimerComplete:onTimerComplete,
          onClick:onClick,
          tag_action_receive_notify:tag_action_receive_notify,
        )
    );
  }

/*
  ElevatedButton,功能强大,用于替代旧的FlatButton、OutlineButton、RaisedButton
  注意: 按钮大小默认自身大小,
  如果外层嵌套Column/Row+Expanded则占满横向或竖向剩余空间
  如果外层是Container/SizedBox,如果不指定width或height,则默认自身大小,如果指定,则自动占满
  内部可以左右结构: getLayoutRow(children:[])
   */
  static Widget getWidgetButtonElevated(
      {
        required Widget child,
        VoidCallback? onPressed,
        VoidCallback? onLongPress,
        ButtonStyle? buttonStyle,
        double? width,//注意不能为double.infinity
        double? height,//注意不能为double.infinity
        EdgeInsets? outsideMargin,//getEdgeInsets(left,top,right,bottom)
        EdgeInsets? outsidePadding,//getEdgeInsets(left,top,right,bottom) //注意:包含在总高度中
        Color? outsideBackgroundColor,//按钮外部背景颜色
        EdgeInsets? buttonInsidePadding,//getEdgeInsets(left,top,right,bottom)
      }
      ){

    //指定宽度
    if(width!=null && width>0){
      return getLayoutContainer(
        child: ElevatedButton(
          onPressed: onPressed,
          onLongPress: onLongPress,
          style: buttonStyle??XStyleUtils.getButtonStyle(),
          child: buttonInsidePadding==null?child:getLayoutPadding(
              padding: buttonInsidePadding,
              child: child
          ),
        ),
        width: width,
        height: height,
        margin: outsideMargin,
        padding: outsidePadding,
        backgroundColor: outsideBackgroundColor,
      );

    }else{
//没有指定宽度,横向占满
      return getLayoutRow(
          crossAxisAlignment:CrossAxisAlignment.center,
          children:[
            getLayoutExpanded(
                getLayoutContainer(
                  child: ElevatedButton(
                    onPressed: onPressed,
                    onLongPress: onLongPress,
                    style: buttonStyle??XStyleUtils.getButtonStyle(),
                    child: buttonInsidePadding==null?child:getLayoutPadding(
                        padding: buttonInsidePadding,
                        child: child
                    ),
                  ),
                  height: height,
                  margin: outsideMargin,
                  padding: outsidePadding,
                  backgroundColor: outsideBackgroundColor,
                )
            )
          ]);
    }

  }


/*
  TextButton 文本按钮(没有边框),透明背景
   */
  static TextButton getWidgetButtonText(
      {
        required String text,
        VoidCallback? onPressed,
        ButtonStyle? buttonStyle,
      }
      ){
    return TextButton(
      onPressed: onPressed,
      style: buttonStyle??XStyleUtils.getButtonStyle(),
      child: Text(text),
    );
  }

  //可设置图标+文字(文字+图标)按钮,
  static Widget getWidgetButtonImage(
      {
        required Widget leftWidget,
        required Widget rightWidget,
        required VoidCallback onPressed,
        ButtonStyle? style,
      }
      ){
    return ElevatedButton.icon(
      onPressed: onPressed,
      icon: leftWidget,
      label: rightWidget,
      style:style??XStyleUtils.getButtonStyle(),
    );
  }

/*
  OutlinedButton 文本按钮,带有边框,透明背景
   */
  static OutlinedButton getWidgetButtonOutlined(
      {
        required Widget child,
        VoidCallback? onPressed,
        ButtonStyle? buttonStyle,
      }
      ){

    return OutlinedButton(
      onPressed: onPressed,
      style: buttonStyle,
      child: child,
    );
  }

/*
  水波纹效果的点击控件(类似按钮)
  设置背景为渐变色:
  BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [Color(0xFFffffff), Color(0xFFEC592F)]),
          borderRadius: BorderRadius.all(Radius.circular(borderRadius))),
   */

  static Widget getWidgetInkWellButton(
      {
        required Widget child,
        required GestureTapCallback onTap,
//边框圆角半径
        double borderRadius=2,
//背景颜色
        Color? backgroundColor,
//点击时水波纹颜色
        Color? splashColor,
      }
      ){

    return Ink(
      decoration: BoxDecoration(
          color: backgroundColor,
          borderRadius: BorderRadius.all(Radius.circular(borderRadius))),
      child: InkWell(
        borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
        onTap: onTap,
        splashColor: splashColor,
        child: child,
      ),
    );
  }


  /*
DropdownButton:
DropdownButton 显示为一个按钮和一个下拉菜单。
当点击按钮时，下拉菜单将从按钮下方展开，并且可以选择其中的项。
通常在表单中使用，用于选择一个或多个选项。
默认情况下，选定的项将显示在按钮上方。
可以通过 items 属性提供菜单项的列表。
支持自定义的 onChanged 回调，以便在选择不同的项时触发相应的操作。

PopupMenuButton:
PopupMenuButton 显示为一个图标按钮。
当点击按钮时，弹出一个上下文菜单，以显示选择项。
弹出的菜单可以以任何你想要的方式进行自定义，可以是文字、图标、分隔线等。
常用于实现更复杂的菜单布局，例如右上角的三点菜单或导航栏中的溢出菜单。
可以通过 itemBuilder 属性动态构建菜单项。

需要注意的是，虽然 DropdownButton 和 PopupMenuButton 在某些方面相似，但它们的外观和用途略有不同，你应根据具体的需求选择合适的小部件来实现所需的下拉菜单效果。
   */

/*
  下拉按钮 DropdownButton
   */
  static DropdownButton<dynamic> getWidgetDropdownButton(
      {
        required List<dynamic> itemData,
//当前选择变量值,外面传入的变量,在onChanged中改变,注意:当前值需要在itemData中存在,否则报错
        required dynamic currentSelectItemValue,
        required XFunctionWidget<dynamic> buildItemWidget,
/*
        选择后改变值回调:
        onChanged:(dynamic item){
            XLogUtils.printLog('--onChanged=$item');
            setState(() {
              currentItemValue=item;
            });
          }
         */
        required ValueChanged<dynamic> onChanged,
        double? menuMaxHeight,
        AlignmentGeometry alignment=AlignmentDirectional.center,
        TextStyle? style,
      }
      ){

//注意:如果currentSelectItemValue不在itemData中会报错
    if(!itemData.contains(currentSelectItemValue)){
//XLogUtils.printLog('---currentSelectItemValue在itemData中不存在!');
      currentSelectItemValue=itemData.first;
    }

//
    return DropdownButton<dynamic>(
      //menuWidth:menuWidth, //低版本不支持
      menuMaxHeight:menuMaxHeight,
      underline:getLayoutContainer(), //外面不显示下横线
      style:style,
      alignment:alignment,
      isExpanded: true,
      items: itemData.map((dynamic item){
        return DropdownMenuItem(
          value: item,
          alignment:alignment,
          child: buildItemWidget(item),
        );
      }).toList(),
      value: currentSelectItemValue,//实测如果不设置则报错
      onChanged: onChanged,
    );
  }


/*
  PopupMenuButton 竖向下拉菜单(类似下拉菜单按钮) 悬浮在上层
  onSelected:(dynamic item){
            XLogUtils.printLog('--onChanged=$item');
            setState(() {
              currentItemValue=item;
            });
          }


例:
XWidgetUtils.getWidgetPopupMenuButtonVertical(
                  width: groupNameWidth,
                  itemData: [
                    //'账户信息',
                    '切换端口', '切换品牌', '选择门店'
                  ],
                  offset: const Offset(0, 0),
                  onSelected: (dynamic value) {
                    if ('账户信息' == value) {
                      startPage(PageStateMine());
                    } else if ('切换端口' == value) {
                      startPage(PageStateChangePort());
                    } else if ('切换品牌' == value) {
                      startPage(PageStateChangePinpai());
                    } else {
                      //选择门店
                      startPage(
                          PageStateSelectStore(
                              XJsonUtils.obj2Json(widget.xStateValue8)),
                          resultCallback: (dynamic result) {
                            if (result != null) {
                              //选择门店更新 ok
                              Map<String, dynamic> jsonMap =
                              XJsonUtils.json2Map(result);
                              widget.xStateValue8 = jsonMap;
                              //更新
                              updateData();
                            }
                          });
                    }
                  },
                  buildItemWidget: (dynamic item) {
                    if ('账户信息' == item) {
                      return XWidgetUtils.getLayoutColumn(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            XWidgetUtils.getLayoutPadding(
                              padding: XWidgetUtils.getEdgeInsets(5, 10, 0, 15),
                              child: XWidgetUtils.getLayoutRow(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    XImageUtils.getImageWidgetWithAsset(
                                        'nav_menu_zh.png',
                                        width: 23,
                                        height: 23),
                                    XWidgetUtils.getLayoutPadding(
                                      padding: XWidgetUtils.getEdgeInsets(
                                          15, 0, 0, 0),
                                      child: XWidgetUtils.getWidgetText(item,
                                          style:
                                          XStyleUtils.textStyle_333_16()),
                                    )
                                  ]),
                            ),
                            XWidgetUtils.getWidgetDivider(
                                marginLeft: 0,
                                marginRight: 0,
                                color: const Color(0xFFEEEEEE))
                          ]);
                    } else if ('切换端口' == item) {
                      return XWidgetUtils.getLayoutColumn(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            XWidgetUtils.getLayoutPadding(
                              padding: XWidgetUtils.getEdgeInsets(5, 15, 0, 15),
                              child: XWidgetUtils.getLayoutRow(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    XImageUtils.getImageWidgetWithAsset(
                                        'nav_menu_pinpai.png',
                                        width: 23,
                                        height: 23),
                                    XWidgetUtils.getLayoutPadding(
                                      padding: XWidgetUtils.getEdgeInsets(
                                          15, 0, 0, 0),
                                      child: XWidgetUtils.getWidgetText(item,
                                          style:
                                          XStyleUtils.textStyle_333_16()),
                                    )
                                  ]),
                            ),
                            XWidgetUtils.getWidgetDivider(
                                marginLeft: 0,
                                marginRight: 0,
                                color: const Color(0xFFEEEEEE))
                          ]);
                    } else if ('切换品牌' == item) {
                      //切换品牌
                      return XWidgetUtils.getLayoutColumn(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            XWidgetUtils.getLayoutPadding(
                              padding: XWidgetUtils.getEdgeInsets(5, 15, 0, 15),
                              child: XWidgetUtils.getLayoutRow(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    XImageUtils.getImageWidgetWithAsset(
                                        'nav_menu_change.png',
                                        width: 23,
                                        height: 23),
                                    XWidgetUtils.getLayoutPadding(
                                      padding: XWidgetUtils.getEdgeInsets(
                                          15, 0, 0, 0),
                                      child: XWidgetUtils.getWidgetText(item,
                                          style:
                                          XStyleUtils.textStyle_333_16()),
                                    )
                                  ]),
                            ),
                            XWidgetUtils.getWidgetDivider(
                                marginLeft: 0,
                                marginRight: 0,
                                color: const Color(0xFFEEEEEE))
                          ]);
                    } else {
                      //选择门店
                      return XWidgetUtils.getLayoutColumn(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            XWidgetUtils.getLayoutPadding(
                              padding: XWidgetUtils.getEdgeInsets(5, 10, 0, 0),
                              child: XWidgetUtils.getLayoutRow(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    XImageUtils.getImageWidgetWithAsset(
                                        'nav_menu_mendian.png',
                                        width: 23,
                                        height: 23),
                                    XWidgetUtils.getLayoutPadding(
                                      padding: XWidgetUtils.getEdgeInsets(
                                          15, 0, 0, 0),
                                      child: XWidgetUtils.getWidgetText(item,
                                          style:
                                          XStyleUtils.textStyle_333_16()),
                                    )
                                  ]),
                            ),
                            //XWidgetUtils.getWidgetDivider(marginLeft: 0,marginRight: 0,color: const Color(0xFFEEEEEE))
                          ]);
                    }
                  },
                  child: XWidgetUtils.getLayoutContainer(
                      height: 48,
                      alignment: Alignment.center,
                      backgroundColor: Colors.white,
                      child: XWidgetUtils.getLayoutRow(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            XWidgetUtils.getLayoutExpanded(
                                XWidgetUtils.getLayoutContainer(
                                    alignment: Alignment.centerRight,
                                    child: XWidgetUtils.getWidgetText(groupName,
                                        maxLines: 1,
                                        style:
                                        XStyleUtils.textStyle_333_14()))),
                            XWidgetUtils.getLayoutContainer(
                                margin: XWidgetUtils.getEdgeInsets(5, 0, 0, 0),
                                child: XImageUtils.getImageWidgetWithAsset(
                                    'down_circle_fill.png',
                                    width: 12,
                                    height: 12.5))
                          ])),
                )

   */
  static Widget getWidgetPopupMenuButtonVertical(
      {
        required List<dynamic> itemData,
        required double width, //实测需要固定宽度(外层显示的宽度),否则不显示菜单
        required XFunctionWidget<dynamic> buildItemWidget,
        required PopupMenuItemSelected<dynamic>? onSelected,
        required Widget? child,//外界自定义显示,可不按选中的值显示
        PopupMenuCanceled? onCanceled,
        Offset offset=Offset.zero,
        EdgeInsets? margin
      }
      ){

    Widget childWidget= getLayoutContainer(
      width: width,
      margin: margin,
      child: PopupMenuButton<dynamic>(
        onSelected: onSelected,
        onCanceled: onCanceled,
        position: PopupMenuPosition.under,
        //initialValue: currentSelectItemValue, //不设置则能保证每次都显示在下方
        offset:offset,
        padding: getEdgeInsets(0, 0, 0, 0),
        constraints:BoxConstraints(
            minWidth:width,
            maxWidth: width
        ),//不设置则使用默认宽度
        itemBuilder: (BuildContext context){
          return itemData.map((dynamic item){
            return PopupMenuItem<dynamic>(
              value: item,//使得onSelected有效
              child: buildItemWidget(item),
            );
          }).toList();
        },
        child: child,
      ),
    );

    return childWidget;
  }

  /*
  下拉菜单 横向 (内部使用listview,点击事件设在listviewItem中)
  代码开启: popupMenuButton.showButtonMenu();
  代码关闭:  通过 key 关闭菜单:
  final dynamic popupMenuState = _popupMenuKey.currentState;
  popupMenuState.close();

   */
  static PopupMenuButton<dynamic> getWidgetPopupMenuButtonHorizontal(
      {
        required List<dynamic> itemData,
        required XFunctionWidget<dynamic> itemWidgetBuilder,
        required XFunctionWidgetOnClick<dynamic> itemOnClick,
        required Widget? child,//外界自定义显示,可不按选中的值显示
        EdgeInsets? padding,
        Offset offset=Offset.zero,
        required double width, //menu的宽度
        double itemHeight=48,//item的高度
        Color backgroundColor=Colors.white,
        bool isItemWidthExpanded=true, //每个item的宽度是否均分占满整个宽度(listview不滚动,适用item数量少的情况), 如果为false,则可以滚动,适用数量多的情况
      }
      ){

    return PopupMenuButton<dynamic>(
      //key: popupMenuKey,//GlobalKey();
      offset:offset,
      position: PopupMenuPosition.under,
      //elevation: 0,//ok
      constraints:BoxConstraints(
        // minHeight: 20,
        // maxHeight: totalHeight, //不设置高度,则自动(上下各10,正常显示)
          minWidth:60,
          maxWidth: width
      ),
      enableFeedback:false,
      surfaceTintColor:Colors.transparent,
      padding: getEdgeInsets(0, 0, 0, 0),
      itemBuilder: (BuildContext context){

        Widget menuWidget=isItemWidthExpanded?getLayoutRow(
            children: itemData.map((dynamic item){
              return getLayoutExpanded(
                  getWidgetGestureDetector(
                      child: itemWidgetBuilder(item),
                      onTap: (){
                        //关闭菜单:
                        Navigator.maybePop(context);
                        //
                        itemOnClick(item);
                      }
                  )
              );
            }).toList())
            :ListView(
          scrollDirection: Axis.horizontal,
          children: itemData.map((dynamic item){
            return itemWidgetBuilder(item);
          }).toList(),
        );

        return <PopupMenuEntry<String>>[
          PopupMenuItem<String>(
            height: itemHeight,
            padding: getEdgeInsets(0, 0, 0, 0),
            child: getLayoutContainer(
                height: itemHeight,
                width: width,
                padding: padding,
                // decoration:getBoxDecoration(
                //   backgroundColor: backgroundColor,
                //   border: Border.all(color:XColorUtils.getColorCCCCCC(), style: BorderStyle.solid, width:0.5),
                //   borderRadius: const BorderRadius.all(Radius.circular(5))
                // ),
                alignment: Alignment.topCenter,
                backgroundColor: backgroundColor,
                child:menuWidget
            ),
          ),
        ];
      },
      child: child,
    );
  }

  //自定义背景下拉菜单
  static Widget getWidgetPopupMenuButtonWidthBackgroundImage(
      {
        required List<dynamic> itemData,
        required XFunctionWidget<dynamic> itemWidgetBuilder,
        required double width, //menu的宽度 100
        required Widget? child,//外界自定义显示,可不按选中的值显示
        required String backgroundImageNameInAssets,//
        Rect centerSlice=const Rect.fromLTRB(5, 5, 5, 5), // 设置拉伸区域（左上右下)
        required XFunctionWidgetOnClick<dynamic> itemOnClick,
        EdgeInsets padding=const EdgeInsets.fromLTRB(0, 0, 0, 0),
        Offset offset=Offset.zero,
        double itemHeight=48,//item的高度
        Color itemWidgetBackgroundColor=Colors.white, //透明则空白处不能点击
      }
      ){

    //总高度
    double totalItemsHeight=itemHeight * itemData.length+itemData.length;
    double totalHeight=totalItemsHeight+padding.top+padding.bottom;
    double totalWidth=width+padding.left+padding.right;

    return getLayoutContainer(
        width: totalWidth,
        child: PopupMenuButton<dynamic>(
          constraints:BoxConstraints(
            // minHeight: totalHeight,
            // maxHeight: totalHeight, //不设置高度,则自动(上下各10,正常显示)
              minWidth:totalWidth,
              maxWidth: totalWidth
          ),
          offset:offset,
          position: PopupMenuPosition.under,
          elevation: 0,
          enableFeedback:false,
          surfaceTintColor:Colors.transparent,
          padding: getEdgeInsets(0, 0, 0, 0),
          itemBuilder: (BuildContext context){
            List<Widget> itemsWidget=[];
            int size= itemData.length;
            for(int i=0;i<size;i++){
              dynamic item=itemData[i];
              Widget itemWidget=getWidgetGestureDetector(
                child: getLayoutContainer(
                  child: itemWidgetBuilder(item),
                  width: width,
                  height: itemHeight,
                  backgroundColor: itemWidgetBackgroundColor,
                ),
                onTap: (){
                  //关闭菜单:
                  Navigator.maybePop(context);
                  itemOnClick(item);
                },
              );
              itemsWidget.add(itemWidget);
              //
              if(i!=size-1){
                itemsWidget.add(getWidgetDivider(marginLeft: 0,marginRight: 0));
              }

            }

            return <PopupMenuEntry<String>>[
              PopupMenuItem<String>(
                height: totalHeight,
                padding: getEdgeInsets(0, 0, 0, 0),
                child: getLayoutContainer(
                    height: totalHeight,
                    padding: padding,
                    decoration:getBoxDecoration(
                      backgroundImageNameInAssets: backgroundImageNameInAssets,
                      centerSlice:centerSlice,
                      fit: BoxFit.fill,
                      backgroundImageWidth: XNumberUtils.d2int(totalWidth),
                      backgroundImageHeight: XNumberUtils.d2int(totalHeight),
                      //border: Border.all(color:XColorUtils.getColorCCCCCC(), style: BorderStyle.solid, width:0.5),
                      //borderRadius: const BorderRadius.all(Radius.circular(5))
                    ),
                    child:getLayoutColumn(children: itemsWidget)
                ),
              ),
            ];
          },
          child: child,
        )
    );

  }


  //悬浮按钮在Scaffold中的子控件
  //其他:https://github.com/Darshan0/FloatingActionBubble
  static FloatingActionButton getWidgetFloatingActionButton(
      {
        Widget? child,
        String? label,
        required VoidCallback onPressed,
        Color? backgroundColor,
      }
      ){
    if(XStringUtils.isEmpty(label)){
      return FloatingActionButton(
        onPressed: onPressed,
        backgroundColor: backgroundColor,
        foregroundColor: backgroundColor,
        child: child,
      );
    }else{
      return FloatingActionButton.extended(
        onPressed: onPressed,
        backgroundColor: backgroundColor,
        foregroundColor: backgroundColor,
        label:Text(label!),
        icon: child,
      );
    }

  }




//输入框TextField控件///////////////////////////////////////////////

  //隐藏输入框
  static void hideKeyboard(BuildContext context){
    FocusScope.of(context).requestFocus(FocusNode());
  }

  static void requestFocusForTextField(
      BuildContext context,
      FocusNode focusNode,
      ){
    FocusScope.of(context).requestFocus(focusNode);
  }

  /*
  监听键盘输入
  注意:当focusNode获得焦点是才能监听,如果失去焦点则需要重新获取焦点才能监听
   */
  static Widget getLayoutRawKeyboardListener(
      {
        required Widget child,
        FocusNode? focusNode,
        ValueChanged<KeyEvent>? onKeyEvent,
      }
      ){
    return KeyboardListener(
      autofocus: true,
      focusNode: focusNode??FocusNode(),
      onKeyEvent: onKeyEvent,
      child: child,
    );
  }

  /*
  单行输入框
  其他: 可以用TextFormField替代TextField,更方便设置默认值等

  注意: 可使用_textEditingController?.clear();清除已输入的内容,不需要界面setState()

  获得焦点: focusNode.requestFocus()  会弹出键盘
  FocusScope.of(context).requestFocus(focusNode);
   */

  static Widget getWidgetTextField(
      {
        String? initialValue,
        bool isPaddingZero=true,//去掉默认间隔
        bool enabled=true,//输入框是否可用，默认为 true
        bool readOnly=false,//true时禁用系统键盘,使用自定义键盘输入
        bool autofocus=false, //设置true时, 刷新界面输入法不会关闭,自动获得焦点
        bool isShowInputText=true,//是否显示输入内容(默认显示明文;当为false时,用*号替代明文,适用输入密码时)
        TextInputType keyboardType=TextInputType.text,//输入类型
        List<TextInputFormatter>? inputFormatters,//输入限制
        TextInputAction textInputAction=TextInputAction.done,//键盘回车键文本显示
        TextStyle? style,//字体样式
        TextAlign textAlign=TextAlign.start,
        String? hintText,//提示语，位于输入框上方(不会消失,当高度不够时,会占用输入框位置)
        TextStyle? labelTextStyle,
        String? labelText,//提示语，位于输入框上方(不会消失,当高度不够时,会占用输入框位置)
        TextStyle? hintTextStyle,
        String? helperText,//辅助文本，位于输入框下方，errorText 为空时显示
        TextStyle? helperStyle,
        /*
        //用于获得焦点,借助Listener FocusNode accountFocus=FocusNode();
        点击空白处关闭输入框,传入xPageState的focusNode_keyboard_textField
        实测不能多一个textfield共用一个
         */
        FocusNode? focusNode,
        Widget? icon,//位于输入框外侧坐标的图标
        Widget? prefixIcon,//头部图标，位于输入框内部最左侧
        Widget? suffixIcon,//尾部图标，位于输入框内部最右侧
        GestureTapCallback? onTap, //输入框点击监听, //输入框点击监听
        ValueChanged<String>? onChanged,//输入内容改变监听
        ValueChanged<String>? onSubmitted,//提交按钮点击回调
/*
        控制器，
        可以控制 textField 的输入内容，设置默认值,改变输入内容,获得输入内容:  _controller.text;
        也可以监听 textField 改变,替代onChanged等
        TextEditingController _controller = TextEditingController(
      text: "123333",
    );
    //监听内容,也可用TextField中的onChange替代
    _controller.addListener(() {
      print(_controller.text);
    });
         */
        TextEditingController? controller,
        int? showLines, //设置显示行数
        int? maxLength,//文本内容最大长度,在底部会显示字数统计
        TextStyle? counterTextStyle,//字数统计
        TextAlignVertical textAlignVertical=TextAlignVertical.top,
        Color? cursorColor,
      }
      ){

    controller??=TextEditingController(text: initialValue);

    TextField textField= TextField(
      textAlign: textAlign,
      focusNode:focusNode,
      autofocus: autofocus,
      readOnly:readOnly,//是否弹出系统键盘
      style: style??XStyleUtils.getTextStyle(
          fontSize: XTempData.defaultTextFontSize,
          color: XColorUtils.color333333
      ),
      textAlignVertical: textAlignVertical,
      keyboardType: keyboardType,
      inputFormatters: inputFormatters,
      textInputAction: textInputAction,
      onTap:onTap,
      // onTap: (){
      //   if(focusNode!=null){
      //     //设置外部的autofocus=true再刷新界面
      //   }
      // },
      onChanged: onChanged,
      onSubmitted: onSubmitted,
      //onAppPrivateCommand: onAppPrivateCommand,//实测与onSubmitted不能共用
      //onEditingComplete: onEditingComplete,//实测:无效
      //onTapOutside: onTapOutside,//实测:只要光标停留在输入框,每次点击外界都会回调,不符合交互
      controller: controller,
      maxLength: maxLength,
      maxLines: isShowInputText?showLines:1,//显示可视行数,注意:隐藏明文时,不能多行
      minLines: isShowInputText?showLines:null,//显示可视行数注意:隐藏明文时,不能多行
// cursorColor: ,//光标颜色
// cursorWidth: ,//光标宽度
// cursorRadius: ,//光标圆角
// cursorHeight: ,//光标高度
      decoration: InputDecoration( //装饰: 提示等
//filled: true,
        hintText: hintText, //使用labelText替代
        hintStyle: hintTextStyle??XStyleUtils.getTextStyle(
            fontSize: XTempData.defaultTextFontSize,
            color: XColorUtils.colorCCCCCC
        ),
        labelText: labelText,
        labelStyle: labelTextStyle??XStyleUtils.getTextStyle(
            fontSize: XTempData.defaultTextFontSize,
            color: XColorUtils.colorCCCCCC
        ),
        counterStyle: counterTextStyle??const TextStyle(
          color: XColorUtils.colorCCCCCC, // 设置字数统计的字体颜色
          fontSize: 12,      // 可选：设置字体大小
        ),
//auto : labelText 显示在输入框中，当开始输入时，会有一个动画，字体变小并显示在输入框上方。 never : labelText 显示在输入框中，当开始输入时，labelText 隐藏。 always: LabelText 永远显示在最上方。
        floatingLabelBehavior: FloatingLabelBehavior.auto,
        helperText: helperText,
        helperStyle: helperStyle,
        icon:icon,
        prefixIcon:prefixIcon,
        suffixIcon: suffixIcon,
        isCollapsed:!XStringUtils.isEmpty(labelText)?false:isPaddingZero, //isPaddingZero=true,默认间隔为0
        //contentPadding: EdgeInsets.zero, //自定义默认间隔
        enabled:enabled,
        isDense: true,//超出1行会自动移动文字,高度完整,否则高度只有一半显示不完整
        border:InputBorder.none, //取消默认下横线
// counter | 备注组件 Widget，位于输入框右下角外侧，与 counterText 不能同时使用
// counterText | 备注文本，位于输入框右下角外侧，与 counter 不能同时使用
// counterStyle | 备注文本样式 TextStyle

      ),

//是否显示明文
      obscureText:!isShowInputText,
      cursorColor: cursorColor,
      showCursor: true,//显示光标
    );



    //
    // if(focusNode!=null){
    //   return getWidgetListener(
    //       child:textField,
    //       onPointerDown: (PointerDownEvent event){
    //         XLogUtils.printLog('---ed onclick 获得焦点');
    //         FocusScope.of(context).requestFocus(focusNode);
    //       }
    //   );
    // }else{
    //   return textField;
    // }

    return textField;

  }

  //多行输入框
  static TextFormField getWidgetTextFormField(
      {
        String? initialValue,//默认值,与controller二选一,同时存在会报错
        TextEditingController? controller,//与initialValue二选一,同时存在会报错
        int? maxLength,//文本内容最大长度
        int? maxLines,//最大行数
        bool enabled=true,//输入框是否可用，默认为 true
        bool autofocus=false, //设置true时, 刷新界面输入法不会关闭,自动获得焦点
        bool isShowInputText=true,//是否显示输入内容(默认显示明文;当为false时,用*号替代明文,适用输入密码时)
        TextInputType keyboardType=TextInputType.text,//输入类型
        TextInputAction textInputAction=TextInputAction.done,//键盘回车键文本显示
        TextStyle? style,//字体样式
        String? labelText,//提示语，位于输入框上方
        TextStyle? labelTextStyle,
        String? helperText,//辅助文本，位于输入框下方，errorText 为空时显示
        TextStyle? helperStyle,
        Widget? icon,//位于输入框外侧坐标的图标
        Widget? prefixIcon,//头部图标，位于输入框内部最左侧
        Widget? suffixIcon,//尾部图标，位于输入框内部最右侧
        GestureTapCallback? onTap, //输入框点击监听
        void Function(String)? onChanged,//输入内容改变监听
        void Function(String)? onFieldSubmitted,//提交按钮点击回调
        //void Function()? onEditingComplete, //点击键盘完成按钮回调
/*
        控制器，
        可以控制 textField 的输入内容，设置默认值,改变输入内容,获得输入内容:  _controller.text;
        也可以监听 textField 改变,替代onChanged等
        TextEditingController _controller = TextEditingController(
      text: "123333",
    );
    _controller.addListener(() {
      print(_controller.text);
    });
         */

      }
      ){

    return TextFormField(
      initialValue: initialValue,//与controller二选一,同时存在会报错
      controller: controller,//与initialValue二选一,同时存在会报错
      maxLength: maxLength,
      maxLines: maxLines,
//是否显示明文
      obscureText:!isShowInputText,
      autofocus:autofocus,
// cursorColor: ,//光标颜色
// cursorWidth: ,//光标宽度
// cursorRadius: ,//光标圆角
// cursorHeight: ,//光标高度
      showCursor:true,
      enabled: enabled,
      keyboardType:keyboardType,
      textInputAction:textInputAction,
      style:style,
      decoration: InputDecoration( //装饰: 提示等
//filled: true,
// hintText: hintText, //使用labelText替代
// hintStyle: hintTextStyle,
        labelText: labelText,
        labelStyle: labelTextStyle,
//auto : labelText 显示在输入框中，当开始输入时，会有一个动画，字体变小并显示在输入框上方。 never : labelText 显示在输入框中，当开始输入时，labelText 隐藏。 always: LabelText 永远显示在最上方。
        floatingLabelBehavior: FloatingLabelBehavior.auto,
        helperText: helperText,
        helperStyle: helperStyle,
        icon:icon,
        prefixIcon:prefixIcon,
        suffixIcon: suffixIcon,
// counter | 备注组件 Widget，位于输入框右下角外侧，与 counterText 不能同时使用
// counterText | 备注文本，位于输入框右下角外侧，与 counter 不能同时使用
// counterStyle | 备注文本样式 TextStyle
        enabled:enabled,
//取消默认下横线
        border:InputBorder.none,
      ),
      onTap: onTap,
      onChanged: onChanged,
      //onEditingComplete: onEditingComplete,
      onFieldSubmitted: onFieldSubmitted,
//前置检验
// validator: (value){
//   if(XStringUtils.isEmpty(value)){
//     return '请输入内容';
//   }
//   return null;
// },


    );
  }




//文本Text控件///////////////////////////////////////////////
//鼠标悬停文字后显示详细提示
  static Tooltip getWidgetTooltip(
      {
//内容
        required String text,
//提示
        required String tooltip,
        TextStyle? textStyle,//字体样式
        TextStyle? tooltipStyle,//字体样式
/*
        背景色,形状
        BoxDecoration(
        borderRadius: BorderRadius.circular(25),
        gradient:
        const LinearGradient(colors: <Color>[Colors.amber, Colors.red]),
      )
         */
        BoxDecoration? decoration,
        double? height,
/*
        const EdgeInsets.all(8.0)
         */
        EdgeInsetsGeometry? padding,
      }
      ){
    return Tooltip(
      message: tooltip,
      decoration: decoration,
      height: height,
      padding: padding,
      textStyle: tooltipStyle,
//preferBelow: false,
//showDuration: const Duration(seconds: 2),
//waitDuration: const Duration(seconds: 1),
      child: getWidgetText(text,style: textStyle),
    );
  }

//一种左右结构的文字(圆角),左边为圆形标签文字(短),右边也是文字(稍长)
  static Chip getWidgetChipText(
      {
        required String leftShortText,
        required String rightText,
        required Color leftBackgroundColor,
        TextStyle? leftTextStyle,//字体样式
        TextStyle? rightTextStyle,//字体样式
        Color? rightBackgroundColor,
      }
      ){
    return Chip(
      avatar: CircleAvatar(
        backgroundColor: leftBackgroundColor??Colors.grey.shade800,
        child: Text(leftShortText,style: leftTextStyle,textAlign:TextAlign.center),
      ),
      label: Text(rightText),
      backgroundColor: rightBackgroundColor,
      labelStyle: rightTextStyle,
    );
  }

//一种左右结构的文字(圆角),左边为图标,右边也是文字(稍长)
  static Chip getWidgetChipIconText(
      {
        required Widget leftWidget,
        required String rightText,
        TextStyle? rightTextStyle,//字体样式
        Color? rightBackgroundColor,
      }
      ){
    return Chip(
      avatar: leftWidget,
      label: Text(rightText),
      backgroundColor: rightBackgroundColor,
      labelStyle: rightTextStyle,
    );
  }

  /*
  基本文字显示控件
  动画: https://pub-web.flutter-io.cn/packages/widget_and_text_animator
   */
  static Widget getWidgetText(
      String str,
      {
        TextStyle? style,//字体样式
        TextAlign textAlign=TextAlign.left,//对齐方式
        int? maxLines,//设置最大行数,如果>0,则自动设置softWrap=false,已maxLines为准;只设置softWrap为false,不设置maxLines则默认1行
        bool isChineseWordAddCode=true, //是否处理中文字符添加零宽空格字符
        EdgeInsets? padding,
      }
      ){

    if(maxLines!=null){
      if(maxLines<=0){
        maxLines=null;
      }
    }

    //注意: TextOverflow.ellipsis省略号只能处理英文符号字符,处理中文会乱码,
// 解决中文字符串超出范围自动省略号问题在每个中文字符之间插入零宽空格字符,即能被TextOverflow.ellipsis处理
    String chineseWord='';
    if(maxLines!=null && maxLines>0 && isChineseWordAddCode){
      for (var element in str.runes) {
        chineseWord+=String.fromCharCode(element);
        chineseWord+='\u200B';
      }
    }

    Text text= Text(
      (maxLines!=null && maxLines>0 && isChineseWordAddCode)?chineseWord:str,
      maxLines: maxLines,
      style: style??XStyleUtils.getTextStyle(
          fontSize: XTempData.defaultTextFontSize,
          color: XColorUtils.color333333
      ),
//softWrap: softWrap, //是否换行, 这里直接设置overflow,以其值为准
      overflow: (maxLines!=null && maxLines>0 && isChineseWordAddCode)?TextOverflow.ellipsis:TextOverflow.visible, //溢出控件大小处理 , TextOverflow.ellipsis 省略号,  clip 裁剪, fade 淡出, visible 依然可视
      textAlign: textAlign,
      textScaler: TextScaler.noScaling, //缩放因子,不随系统字体大小改变
    );

    if(padding!=null){
      return getLayoutPadding(
          padding: padding,
          child: text
      );
    }else{
      return text;
    }


  }

  /*
  富文本

  方法1:
  Text.rich(
  TextSpan(
    text: 'Hello ',
    style: TextStyle(fontSize: 20),
    children: <InlineSpan>[
      TextSpan(text: 'beautiful', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.red)),
      TextSpan(text: ' world!', style: TextStyle(fontSize: 24)),
      WidgetSpan(...)
    ],
  ),
)
方法2:由于RichText比较灵活复杂，因此它的性能也相对较低，所以建议只在必要时才使用RichText来实现富文本效果
RichText(
  text: TextSpan(
    style: TextStyle(color: Colors.black, fontSize: 16),
    children: <TextSpan>[
      TextSpan(text: 'Hello', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ', '),
      TextSpan(text: 'world!', style: TextStyle(fontStyle: FontStyle.italic)),
    ],
  ),
)
嵌入widget:WidgetSpan
Text.rich(
  TextSpan(
    text: 'This is a ',
    style: TextStyle(fontSize: 16),
    children: <InlineSpan>[
      WidgetSpan(
        child: Icon(Icons.favorite, size: 20, color: Colors.red),
        alignment: PlaceholderAlignment.middle,
      ),
      TextSpan(text: ' Flutter app.'),
    ],
  ),
)


其他:TextPainter
   */
  static getWidgetRichText(
      List<TextSpan> textSpans,
      {
        TextAlign textAlign=TextAlign.start
      }
      ){
    return RichText(
      textAlign:textAlign,
      text: TextSpan(
        style: XStyleUtils.getTextStyle(
            fontSize: XTempData.defaultTextFontSize,
            color: XColorUtils.color333333
        ),
        children: textSpans,
      ),
    );

  }


  //动态波浪 百分比
  static Widget getWidgetWave(
      {
        required double width,
        required double height,
        required double currentWaveHeight,
        bool isCircleLayout=true,
        String? text,
        double fontSize=18,
        Color backgroundColor=Colors.white,
        Color? waveColor,
        EdgeInsets? margin,
        EdgeInsets? padding,
      }
      )
  {

    return getLayoutContainer(
        width: width,
        height: height,
        padding: padding,
        margin: margin,
        child: XStatefulWidget(
                () => XWaveState(
              text: text,
              currentWaveHeight:currentWaveHeight,
              fontSize: fontSize,
              backgroundColor: backgroundColor,
              waveColor: waveColor??XColorUtils.appMainColor,
              isCircleLayout:isCircleLayout,
            )
        )
    );
  }


  /*
  保存widget为图片

Future<void> _captureAndSave() async {
    try {
      // 检查存储权限
      if (await Permission.storage.request().isGranted) {
        // 获取Render对象
        RenderRepaintBoundary boundary =
            _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
        // 转换为图片
        ui.Image image = await boundary.toImage(pixelRatio: 3.0); // pixelRatio控制清晰度
        // 转换为PNG字节
        ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
        Uint8List pngBytes = byteData!.buffer.asUint8List();

        // 保存到文件
        final directory = await getApplicationDocumentsDirectory();
        final String path = '${directory.path}/captured_image.png';
        File file = File(path);
        await file.writeAsBytes(pngBytes);

        // 保存到相册,使用第三方:image_gallery_saver

      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('保存失败: $e')),
      );
    }
  }

  RenderRepaintBoundary boundary =
        _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
    ui.Image image = await boundary.toImage();
    ByteData? byteData =
        await (image.toByteData(format: ui.ImageByteFormat.png));
    if (byteData != null) {
      final result =
          await ImageGallerySaverPlus.saveImage(byteData.buffer.asUint8List());
      print(result);
    }


   */
  static Widget getLayoutRepaintBoundary(
      {
        required Key key, // GlobalKey keyRepaintBoundary = GlobalKey();用于定位要捕获的Widget
        required Widget child
      }
      ){

    return RepaintBoundary(
      key: key,
      child: child,
    );
  }



  /*
  自定义双向滚动table
  Table比DataTable功能更简单轻量
  其他: https://pub-web.flutter-io.cn/packages/two_dimensional_scrollables/example
   */
  static Widget getXWidgetTable(
      {
        //列标签
        required List<XBeanDataTableLabel> columnsLabels,
        //行数据
        required List<dynamic> listData,
        required XFunctionTableRowItem rowItemBuildFunction,
        int frozenLeftColumnsCount=0,//冻结左边列数
        int frozenRightColumnsCount=0,//冻结右边列数
        Widget? frozenFooterWidget,
        EdgeInsets? padding,
        double initialVerticalScrollOffset=0,//widget.currentListViewPosition
        double initialHorizontalScrollOffset=0,//widget.currentListViewPosition
        double headingRowHeight=48,
        double itemRowHeight=48,
        TableBorder? tableBorder,
      }
      )
  {
    //
    List<Widget> listWidgets=[];
    //表头
    List<Widget> listViewHeaderWidgets= columnsLabels.map((XBeanDataTableLabel label){
      TextAlign textAlign;
      if(label.alignment==Alignment.centerLeft || label.alignment==Alignment.topLeft || label.alignment==Alignment.bottomLeft){
        textAlign=TextAlign.start;
      }else if(label.alignment==Alignment.centerRight || label.alignment==Alignment.topRight || label.alignment==Alignment.bottomRight){
        textAlign=TextAlign.end;
      }else{
        textAlign=TextAlign.center;
      }

      Widget columnItemWidget=XWidgetUtils.getLayoutContainer(
          width: (label.columnWidth??60)*XTempData.deviceScreenScale,
          height: headingRowHeight*XTempData.deviceScreenScale,
          backgroundColor: label.backgroundColor,
          child: XWidgetUtils.getWidgetText(
            label.text,
            textAlign: textAlign,
            style: label.style,
          ),
          alignment:label.alignment
      );

      return columnItemWidget;

    }).toList();
    //
    listWidgets.add(
        XWidgetUtils.getLayoutContainer(
            child: XWidgetUtils.getLayoutRow(children: listViewHeaderWidgets)
        )
    );

    //数据表格DataTable(支持横向滑动)
    if(frozenLeftColumnsCount==0 && frozenRightColumnsCount==0){
      //没有冻结列数
      // listWidgets.add(
      //     getLayoutSingleChildScrollView(
      //         scrollDirection:Axis.horizontal,
      //         initialScrollOffset: initialHorizontalScrollOffset,
      //         child: getLayoutContainer(
      //           child: Table(
      //             border: tableBorder,
      //             children: listData.map(
      //                     (dynamic item){
      //                       return rowItemBuildFunction(item);
      //                     }
      //             ).toList(),
      //           )
      //         )
      //     )
      // );
    }

    //冻结底部widget
    if(frozenFooterWidget!=null){
      listWidgets.add(frozenFooterWidget);
    }

    return XWidgetUtils.getLayoutSingleChildScrollView(
      padding:padding,
      //支持分页
      initialScrollOffset: initialVerticalScrollOffset,
      // onNotifyScrollStop: !getIsEnablePageLoadData()?null:(ScrollController? scrollController){
      //   //启用分页加载数据
      //   widget.currentListViewPosition=scrollController?.offset??0.0;
      // },
      // onNotifyScrollBottomPosition: !getIsEnablePageLoadData()?null:(e){
      //   //滑动到底部通知,加载下一页
      //   if(widget.currentPageIndex<widget.totalPages){
      //     updateData(pageIndex: widget.currentPageIndex+1);
      //   }
      // },
      // onRefresh: !getIsEnableRefreshLoadData()?null:() async{
      //   widget.currentListViewPosition=0.0;
      //   updateData(pageIndex: getIsPageIndexStartWith1()?1:0);
      // },
      child: XWidgetUtils.getLayoutColumn(children: listWidgets),
    );

  }



}
